diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 503a8e826..c82e759c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,8 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - java: [ 1.8, 11 ] + java: + - 11 steps: - name: Checkout diff --git a/build.gradle b/build.gradle index fff63fa5a..47e5e334e 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,29 @@ plugins { apply plugin: 'org.kordamp.gradle.markdown' +ext { + java11Projects = [ + ':smack-integration-test', + ':smack-omemo-signal-integration-test', + ':smack-repl', + ':smack-websocket-java11', + ].collect { project(it) } + java11Projects += getRootProject() + java8Projects = allprojects - java11Projects +} + +configure (java8Projects) { + ext { + javaCompatilibity = JavaVersion.VERSION_1_8 + } +} + +configure (java11Projects) { + ext { + javaCompatilibity = JavaVersion.VERSION_11 + } +} + allprojects { apply plugin: 'java-library' apply plugin: 'java-test-fixtures' @@ -136,7 +159,6 @@ allprojects { // Default to true useSonatype = true } - javaCompatilibity = JavaVersion.VERSION_1_8 javaMajor = javaCompatilibity.getMajorVersion() } group = 'org.igniterealtime.smack' @@ -338,7 +360,7 @@ task javadocAll(type: Javadoc) { linkSource = true use = true links = [ - "https://docs.oracle.com/javase/${javaMajor}/docs/api/", + "https://docs.oracle.com/en/java/javase/${javaMajor}/docs/api/", "https://jxmpp.org/releases/${staticJxmppVersion}/javadoc/", "https://minidns.org/releases/${staticMiniDnsVersion}/javadoc/", ] as String[] diff --git a/settings.gradle b/settings.gradle index 17cdc06bd..91c403b77 100644 --- a/settings.gradle +++ b/settings.gradle @@ -32,6 +32,7 @@ include 'smack-core', 'smack-openpgp', 'smack-websocket', 'smack-websocket-okhttp', + 'smack-websocket-java11', 'smack-xmlparser', 'smack-xmlparser-stax', 'smack-xmlparser-xpp3' diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/XmppClientToServerTransport.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/XmppClientToServerTransport.java index a4f0db206..d1153c119 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/XmppClientToServerTransport.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/XmppClientToServerTransport.java @@ -54,8 +54,6 @@ public abstract class XmppClientToServerTransport { public abstract SSLSession getSslSession(); - public abstract boolean isConnected(); - public boolean isTransportSecured() { return getSslSession() != null; } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpoint.java b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpoint.java index 0fc31e85f..3356d1324 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpoint.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpoint.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Florian Schmaus + * Copyright 2020-2021 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,10 @@ public interface RemoteConnectionEndpoint { String getDescription(); + default String getRawString() { + return toString(); + } + class InetSocketAddressCoupling { private final RCE connectionEndpoint; private final InetSocketAddress inetSocketAddress; diff --git a/smack-integration-test/build.gradle b/smack-integration-test/build.gradle index d92efbbae..5c57fe9a5 100644 --- a/smack-integration-test/build.gradle +++ b/smack-integration-test/build.gradle @@ -9,6 +9,7 @@ applicationDefaultJvmArgs = ["-enableassertions"] dependencies { api project(':smack-java8-full') api project(':smack-resolver-dnsjava') + implementation project(':smack-websocket-java11') implementation "com.google.guava:guava:${guavaVersion}" compile 'org.reflections:reflections:0.9.12' compile 'eu.geekplace.javapinning:java-pinning-java7:1.1.0-alpha1' diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackSpecificLowLevelIntegrationTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackSpecificLowLevelIntegrationTest.java index 492f04c7e..25978985b 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackSpecificLowLevelIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackSpecificLowLevelIntegrationTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2018-2020 Florian Schmaus + * Copyright 2018-2021 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,15 +30,12 @@ public abstract class AbstractSmackSpecificLowLevelIntegrationTest connectionClass; - private final XmppConnectionDescriptor> connectionDescriptor; public AbstractSmackSpecificLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment, Class connectionClass) { super(environment); this.environment = environment; - this.connectionClass = connectionClass; connectionDescriptor = environment.connectionManager.getConnectionDescriptorFor(connectionClass); if (connectionDescriptor == null) { @@ -46,8 +43,8 @@ public abstract class AbstractSmackSpecificLowLevelIntegrationTest getConnectionClass() { - return connectionClass; + public XmppConnectionDescriptor> getConnectionDescriptor() { + return connectionDescriptor; } protected C getSpecificUnconnectedConnection() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java index fe31f391d..d64dda1b0 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java @@ -48,6 +48,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.jivesoftware.smack.AbstractXMPPConnection; +import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.Smack; import org.jivesoftware.smack.SmackConfiguration; @@ -199,8 +200,8 @@ public class SmackIntegrationTestFramework { Set> inttestClasses = reflections.getSubTypesOf(AbstractSmackIntegrationTest.class); Set> lowLevelInttestClasses = reflections.getSubTypesOf(AbstractSmackLowLevelIntegrationTest.class); - Set> classes = new HashSet<>(inttestClasses.size() - + lowLevelInttestClasses.size()); + final int builtInTestCount = inttestClasses.size() + lowLevelInttestClasses.size(); + Set> classes = new HashSet<>(builtInTestCount); classes.addAll(inttestClasses); classes.addAll(lowLevelInttestClasses); @@ -333,11 +334,11 @@ public class SmackIntegrationTestFramework { continue; } - Class specificLowLevelConnectionClass = null; + XmppConnectionDescriptor specificLowLevelConnectionDescriptor = null; final TestType testType; if (test instanceof AbstractSmackSpecificLowLevelIntegrationTest) { AbstractSmackSpecificLowLevelIntegrationTest specificLowLevelTest = (AbstractSmackSpecificLowLevelIntegrationTest) test; - specificLowLevelConnectionClass = specificLowLevelTest.getConnectionClass(); + specificLowLevelConnectionDescriptor = specificLowLevelTest.getConnectionDescriptor(); testType = TestType.SpecificLowLevel; } else if (test instanceof AbstractSmackLowLevelIntegrationTest) { testType = TestType.LowLevel; @@ -366,6 +367,7 @@ public class SmackIntegrationTestFramework { verifyLowLevelTestMethod(method, AbstractXMPPConnection.class); break; case SpecificLowLevel: + Class specificLowLevelConnectionClass = specificLowLevelConnectionDescriptor.getConnectionClass(); verifyLowLevelTestMethod(method, specificLowLevelConnectionClass); break; } @@ -543,10 +545,8 @@ public class SmackIntegrationTestFramework { continue; } - Class connectionClass = connectionDescriptor.getConnectionClass(); - - ConcreteTest.Executor executor = () -> lowLevelTestMethod.invoke(test, connectionClass); - ConcreteTest concreteTest = new ConcreteTest(TestType.LowLevel, lowLevelTestMethod.testMethod, executor, connectionClass.getSimpleName()); + ConcreteTest.Executor executor = () -> lowLevelTestMethod.invoke(test, connectionDescriptor); + ConcreteTest concreteTest = new ConcreteTest(TestType.LowLevel, lowLevelTestMethod.testMethod, executor, connectionDescriptor.getNickname()); resultingConcreteTests.add(concreteTest); } @@ -560,8 +560,9 @@ public class SmackIntegrationTestFramework { if (testMethod.smackIntegrationTestAnnotation.onlyDefaultConnectionType()) { throw new IllegalArgumentException("SpecificLowLevelTests must not have set onlyDefaultConnectionType"); } - Class connectionClass = test.getConnectionClass(); - testMethod.invoke(test, connectionClass); + + XmppConnectionDescriptor> connectionDescriptor = test.getConnectionDescriptor(); + testMethod.invoke(test, connectionDescriptor); } protected SmackIntegrationTestEnvironment prepareEnvironment() throws SmackException, @@ -837,9 +838,8 @@ public class SmackIntegrationTestFramework { parameterListOfConnections = testMethodParametersIsListOfConnections(testMethod); } - // TODO: The second parameter should probably be a connection descriptor? private void invoke(AbstractSmackLowLevelIntegrationTest test, - Class connectionClass) + XmppConnectionDescriptor connectionDescriptor) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InterruptedException, SmackException, IOException, XMPPException { final int connectionCount; @@ -854,7 +854,7 @@ public class SmackIntegrationTestFramework { } List connections = connectionManager.constructConnectedConnections( - connectionClass, connectionCount); + connectionDescriptor, connectionCount); if (parameterListOfConnections) { testMethod.invoke(test, connections); diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionDescriptor.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionDescriptor.java index 6eaec61e0..94226f6c8 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionDescriptor.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionDescriptor.java @@ -1,6 +1,6 @@ /** * - * Copyright 2018-2020 Florian Schmaus + * Copyright 2018-2021 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.igniterealtime.smack.inttest; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -29,7 +30,12 @@ import java.util.List; import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection; +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration; +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModuleDescriptor; import org.jivesoftware.smack.util.Consumer; +import org.jivesoftware.smack.websocket.XmppWebSocketTransportModuleDescriptor; +import org.jivesoftware.smack.websocket.impl.WebSocketFactory; public final class XmppConnectionDescriptor< C extends AbstractXMPPConnection, @@ -134,6 +140,32 @@ public final class XmppConnectionDescriptor< return new Builder<>(connectionClass, connectionConfigurationClass, connectionConfigurationBuilderClass); } + public static XmppConnectionDescriptor buildWebsocketDescriptor( + String nickname, Class factoryClass) + throws InstantiationException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException, SecurityException { + WebSocketFactory factory; + try { + Field instanceField = factoryClass.getField("INSTANCE"); + factory = (WebSocketFactory) instanceField.get(null); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { + factory = factoryClass.getConstructor().newInstance(); + } + WebSocketFactory finalFactory = factory; + + return XmppConnectionDescriptor.buildWith(ModularXmppClientToServerConnection.class, ModularXmppClientToServerConnectionConfiguration.class, ModularXmppClientToServerConnectionConfiguration.Builder.class) + .withNickname("modular-websocket-java11") + .applyExtraConfguration(cb -> { + cb.removeAllModules(); + ModularXmppClientToServerConnectionModuleDescriptor webSocketModuleDescriptor = + XmppWebSocketTransportModuleDescriptor.getBuilder(cb) + .setWebSocketFactory(finalFactory) + .build(); + cb.addModule(webSocketModuleDescriptor); + }) + .build(); + } + public static final class Builder> { private final Class connectionClass; private final Class connectionConfigurationClass; diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java index c380e9831..caac9b0cd 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java @@ -1,6 +1,6 @@ /** * - * Copyright 2018-2020 Florian Schmaus + * Copyright 2018-2021 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -44,8 +45,8 @@ import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smack.util.MultiMap; import org.jivesoftware.smack.util.StringUtils; -import org.jivesoftware.smack.websocket.XmppWebSocketTransportModuleDescriptor; - +import org.jivesoftware.smack.websocket.java11.Java11WebSocketFactory; +import org.jivesoftware.smack.websocket.okhttp.OkHttpWebSocketFactory; import org.jivesoftware.smackx.admin.ServiceAdministrationManager; import org.jivesoftware.smackx.iqregister.AccountManager; @@ -88,15 +89,13 @@ public class XmppConnectionManager { .build() ); addConnectionDescriptor( - XmppConnectionDescriptor.buildWith(ModularXmppClientToServerConnection.class, ModularXmppClientToServerConnectionConfiguration.class, ModularXmppClientToServerConnectionConfiguration.Builder.class) - .withNickname("modular-websocket") - .applyExtraConfguration(cb -> { - cb.removeAllModules(); - cb.addModule(XmppWebSocketTransportModuleDescriptor.class); - }) - .build() + XmppConnectionDescriptor.buildWebsocketDescriptor("modular-websocket-okhttp", OkHttpWebSocketFactory.class) ); - } catch (NoSuchMethodException | SecurityException e) { + addConnectionDescriptor( + XmppConnectionDescriptor.buildWebsocketDescriptor("modular-websocket-java11", Java11WebSocketFactory.class) + ); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { throw new AssertionError(e); } } @@ -155,12 +154,12 @@ public class XmppConnectionManager { /** * A pool of authenticated and free to use connections. */ - private final MultiMap, AbstractXMPPConnection> connectionPool = new MultiMap<>(); + private final MultiMap, AbstractXMPPConnection> connectionPool = new MultiMap<>(); /** * A list of all ever created connections. */ - private final List connections = new ArrayList<>(); + private final Map> connections = new ConcurrentHashMap<>(); XmppConnectionManager(SmackIntegrationTestFramework sinttestFramework) throws SmackException, IOException, XMPPException, InterruptedException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { @@ -249,7 +248,7 @@ public class XmppConnectionManager { void disconnectAndCleanup() throws InterruptedException { int successfullyDeletedAccountsCount = 0; - for (AbstractXMPPConnection connection : connections) { + for (AbstractXMPPConnection connection : connections.keySet()) { if (sinttestConfiguration.accountRegistration == AccountRegistration.inBandRegistration) { // Note that we use the account manager from the to-be-deleted connection. AccountManager accountManager = AccountManager.getInstance(connection); @@ -345,7 +344,7 @@ public class XmppConnectionManager { } }); - connections.add(mainConnection); + connections.put(mainConnection, defaultConnectionDescriptor); mainConnection.connect(); mainConnection.login(); @@ -384,13 +383,13 @@ public class XmppConnectionManager { } } - List constructConnectedConnections(Class connectionClass, int count) + List constructConnectedConnections(XmppConnectionDescriptor> connectionDescriptor, int count) throws InterruptedException, SmackException, IOException, XMPPException { List connections = new ArrayList<>(count); synchronized (connectionPool) { @SuppressWarnings("unchecked") - List pooledConnections = (List) connectionPool.getAll(connectionClass); + List pooledConnections = (List) connectionPool.getAll(connectionDescriptor); while (count > 0 && !pooledConnections.isEmpty()) { C connection = pooledConnections.remove(pooledConnections.size() - 1); connections.add(connection); @@ -398,9 +397,6 @@ public class XmppConnectionManager { } } - @SuppressWarnings("unchecked") - XmppConnectionDescriptor> connectionDescriptor = (XmppConnectionDescriptor>) connectionDescriptors - .getFirst(connectionClass); for (int i = 0; i < count; i++) { C connection = constructConnectedConnection(connectionDescriptor); connections.add(connection); @@ -469,7 +465,7 @@ public class XmppConnectionManager { throw new IllegalStateException(e); } - connections.add(connection); + connections.put(connection, connectionDescriptor); return connection; } @@ -487,8 +483,13 @@ public class XmppConnectionManager { } if (connection.isAuthenticated()) { + XmppConnectionDescriptor connectionDescriptor = connections.get(connection); + if (connectionDescriptor == null) { + throw new IllegalStateException("Attempt to recycle unknown connection: " + connection); + } + synchronized (connectionPool) { - connectionPool.put(connectionClass, connection); + connectionPool.put(connectionDescriptor, connection); } } else { connection.disconnect(); diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/c2s/SimpleXmppConnectionIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/c2s/SimpleXmppConnectionIntegrationTest.java index 8fc63e13a..f56866e0c 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smack/c2s/SimpleXmppConnectionIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/c2s/SimpleXmppConnectionIntegrationTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Aditya Borikar + * Copyright 2021 Florian Schmaus, 2020 Aditya Borikar * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,28 +16,31 @@ */ package org.jivesoftware.smack.c2s; +import java.util.List; import java.util.concurrent.TimeoutException; +import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.StanzaListener; import org.jivesoftware.smack.filter.MessageWithBodiesFilter; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Stanza; -import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; +import org.igniterealtime.smack.inttest.AbstractSmackLowLevelIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest; import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; import org.jxmpp.jid.EntityFullJid; -public class SimpleXmppConnectionIntegrationTest extends AbstractSmackIntegrationTest { +public class SimpleXmppConnectionIntegrationTest extends AbstractSmackLowLevelIntegrationTest { public SimpleXmppConnectionIntegrationTest(SmackIntegrationTestEnvironment environment) { super(environment); } - @SmackIntegrationTest - public void createConnectionTest() throws TimeoutException, Exception { + @SmackIntegrationTest(connectionCount = 2) + public void createConnectionTest(List connections) throws TimeoutException, Exception { + final AbstractXMPPConnection conOne = connections.get(0), conTwo = connections.get(1); EntityFullJid userTwo = conTwo.getUser(); final String messageBody = testRunId + ": Hello from the other side!"; diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java index bac736d37..bd194fcda 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java @@ -664,7 +664,6 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM return tlsState.engine.getSession(); } - @Override public boolean isConnected() { SocketChannel socketChannel = XmppTcpTransportModule.this.socketChannel; if (socketChannel == null) { diff --git a/smack-websocket-java11/build.gradle b/smack-websocket-java11/build.gradle new file mode 100644 index 000000000..9439bad20 --- /dev/null +++ b/smack-websocket-java11/build.gradle @@ -0,0 +1,8 @@ +description = """\ +Smack for XMPP connections over WebSocket (RFC 7395) using java.net.http.WebSocket.""" + +dependencies { + api project(':smack-websocket') + + testFixturesApi(testFixtures(project(':smack-websocket'))) +} diff --git a/smack-websocket-java11/src/main/java/org/jivesoftware/smack/websocket/java11/Java11WebSocket.java b/smack-websocket-java11/src/main/java/org/jivesoftware/smack/websocket/java11/Java11WebSocket.java new file mode 100644 index 000000000..b259a8916 --- /dev/null +++ b/smack-websocket-java11/src/main/java/org/jivesoftware/smack/websocket/java11/Java11WebSocket.java @@ -0,0 +1,164 @@ +/** + * + * Copyright 2021 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.websocket.java11; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.WebSocket; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; + +import javax.net.ssl.SSLSession; + +import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; +import org.jivesoftware.smack.util.LazyStringBuilder; +import org.jivesoftware.smack.websocket.impl.AbstractWebSocket; +import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; + +public final class Java11WebSocket extends AbstractWebSocket { + + private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder().build(); + + private WebSocket webSocket; + + enum PingPong { + ping, + pong, + }; + + Java11WebSocket(WebSocketRemoteConnectionEndpoint endpoint, + ModularXmppClientToServerConnectionInternal connectionInternal) { + super(endpoint, connectionInternal); + + final WebSocket.Listener listener = new WebSocket.Listener() { + @Override + public void onOpen(WebSocket webSocket) { + LOGGER.finer(webSocket + " opened"); + webSocket.request(1); + } + + LazyStringBuilder received = new LazyStringBuilder(); + + @Override + public CompletionStage onText(WebSocket webSocket, CharSequence data, boolean last) { + received.append(data); + webSocket.request(1); + + if (last) { + String wholeMessage = received.toString(); + received = new LazyStringBuilder(); + onIncomingWebSocketElement(wholeMessage); + } + + return null; + } + + @Override + public void onError(WebSocket webSocket, Throwable error) { + onWebSocketFailure(error); + } + + @Override + public CompletionStage onClose(WebSocket webSocket, int statusCode, String reason) { + LOGGER.finer(webSocket + " closed with status code " + statusCode + ". Provided reason: " + reason); + // TODO: What should we do here? What if some server implementation closes the WebSocket out of the + // blue? Ideally, we should react on this. Some situation in the okhttp implementation. + return null; + } + + @Override + public CompletionStage onPing(WebSocket webSocket, ByteBuffer message) { + logPingPong(PingPong.ping, webSocket, message); + + webSocket.request(1); + return null; + } + + @Override + public CompletionStage onPong(WebSocket webSocket, ByteBuffer message) { + logPingPong(PingPong.pong, webSocket, message); + + webSocket.request(1); + return null; + } + + private void logPingPong(PingPong pingPong, WebSocket webSocket, ByteBuffer message) { + final Level pingPongLogLevel = Level.FINER; + if (!LOGGER.isLoggable(pingPongLogLevel)) { + return; + } + + LOGGER.log(pingPongLogLevel, "Received " + pingPong + " over " + webSocket + ". Message: " + message); + } + }; + + final URI uri = endpoint.getUri(); + CompletionStage webSocketFuture = HTTP_CLIENT.newWebSocketBuilder() + .subprotocols(SEC_WEBSOCKET_PROTOCOL_HEADER_FILED_VALUE_XMPP) + .buildAsync(uri, listener); + + webSocketFuture.whenComplete((webSocket, throwable) -> { + if (throwable == null) { + this.webSocket = webSocket; + future.setResult(this); + } else { + onWebSocketFailure(throwable); + } + }); + } + + @Override + protected void send(String element) { + CompletableFuture completableFuture = webSocket.sendText(element, true); + try { + completableFuture.get(); + } catch (ExecutionException e) { + onWebSocketFailure(e); + } catch (InterruptedException e) { + // This thread should never be interrupted, as it is a Smack internal thread. + throw new AssertionError(e); + } + } + + @Override + public void disconnect(int code, String message) { + CompletableFuture completableFuture = webSocket.sendClose(code, message); + try { + completableFuture.get(); + } catch (ExecutionException e) { + onWebSocketFailure(e); + } catch (InterruptedException e) { + // This thread should never be interrupted, as it is a Smack internal thread. + throw new AssertionError(e); + } finally { + webSocket.abort(); + } + } + + @Override + public SSLSession getSSLSession() { + return null; + } + + private void onWebSocketFailure(ExecutionException executionException) { + Throwable cause = executionException.getCause(); + onWebSocketFailure(cause); + } +} diff --git a/smack-websocket-java11/src/main/java/org/jivesoftware/smack/websocket/java11/Java11WebSocketFactory.java b/smack-websocket-java11/src/main/java/org/jivesoftware/smack/websocket/java11/Java11WebSocketFactory.java new file mode 100644 index 000000000..9f221beae --- /dev/null +++ b/smack-websocket-java11/src/main/java/org/jivesoftware/smack/websocket/java11/Java11WebSocketFactory.java @@ -0,0 +1,33 @@ +/** + * + * Copyright 2021 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.websocket.java11; + +import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; +import org.jivesoftware.smack.websocket.impl.WebSocketFactory; +import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; + +public class Java11WebSocketFactory implements WebSocketFactory { + + public static final Java11WebSocketFactory INSTANCE = new Java11WebSocketFactory(); + + @Override + public Java11WebSocket create(WebSocketRemoteConnectionEndpoint endpoint, + ModularXmppClientToServerConnectionInternal connectionInternal) { + return new Java11WebSocket(endpoint, connectionInternal); + } + +} diff --git a/smack-websocket-java11/src/main/java/org/jivesoftware/smack/websocket/java11/package-info.java b/smack-websocket-java11/src/main/java/org/jivesoftware/smack/websocket/java11/package-info.java new file mode 100644 index 000000000..399e4f28f --- /dev/null +++ b/smack-websocket-java11/src/main/java/org/jivesoftware/smack/websocket/java11/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2021 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * WebSocket support for Smack using {@link java.net.http.WebSocket}, which is available since Java 11. + */ +package org.jivesoftware.smack.websocket.java11; diff --git a/smack-websocket-java11/src/main/resources/META-INF/services/org.jivesoftware.smack.websocket.impl.WebSocketFactory b/smack-websocket-java11/src/main/resources/META-INF/services/org.jivesoftware.smack.websocket.impl.WebSocketFactory new file mode 100644 index 000000000..3f5844c83 --- /dev/null +++ b/smack-websocket-java11/src/main/resources/META-INF/services/org.jivesoftware.smack.websocket.impl.WebSocketFactory @@ -0,0 +1 @@ +org.jivesoftware.smack.websocket.java11.Java11WebSocketFactory diff --git a/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocket.java b/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocket.java index 59d2dba7c..a268b2655 100644 --- a/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocket.java +++ b/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocket.java @@ -16,15 +16,11 @@ */ package org.jivesoftware.smack.websocket.okhttp; -import java.net.URI; import java.util.logging.Level; -import java.util.logging.Logger; import javax.net.ssl.SSLSession; -import org.jivesoftware.smack.SmackFuture; import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; -import org.jivesoftware.smack.websocket.WebSocketException; import org.jivesoftware.smack.websocket.impl.AbstractWebSocket; import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; @@ -36,37 +32,21 @@ import okhttp3.WebSocketListener; public final class OkHttpWebSocket extends AbstractWebSocket { - private static final Logger LOGGER = Logger.getLogger(OkHttpWebSocket.class.getName()); - - private static final OkHttpClient okHttpClient = new OkHttpClient(); - - // This is a potential candidate to be placed into AbstractWebSocket, but I keep it here until smack-websocket-java11 - // arrives. - private final SmackFuture.InternalSmackFuture future = new SmackFuture.InternalSmackFuture<>(); - - private final LoggingInterceptor interceptor; + private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient(); private final WebSocket okHttpWebSocket; - public OkHttpWebSocket(WebSocketRemoteConnectionEndpoint endpoint, + OkHttpWebSocket(WebSocketRemoteConnectionEndpoint endpoint, ModularXmppClientToServerConnectionInternal connectionInternal) { super(endpoint, connectionInternal); - if (connectionInternal.smackDebugger != null) { - interceptor = new LoggingInterceptor(connectionInternal.smackDebugger); - } else { - interceptor = null; - } - - final URI uri = endpoint.getUri(); - final String url = uri.toString(); - + final String url = endpoint.getRawString(); Request request = new Request.Builder() .url(url) - .header("Sec-WebSocket-Protocol", "xmpp") + .header(SEC_WEBSOCKET_PROTOCOL_HEADER_FILED_NAME, SEC_WEBSOCKET_PROTOCOL_HEADER_FILED_VALUE_XMPP) .build(); - okHttpWebSocket = okHttpClient.newWebSocket(request, listener); + okHttpWebSocket = OK_HTTP_CLIENT.newWebSocket(request, listener); } private final WebSocketListener listener = new WebSocketListener() { @@ -75,35 +55,18 @@ public final class OkHttpWebSocket extends AbstractWebSocket { public void onOpen(WebSocket webSocket, Response response) { LOGGER.log(Level.FINER, "OkHttp invoked onOpen() for {0}. Response: {1}", new Object[] { webSocket, response }); - - if (interceptor != null) { - interceptor.interceptOpenResponse(response); - } - future.setResult(OkHttpWebSocket.this); } @Override public void onMessage(WebSocket webSocket, String text) { - if (interceptor != null) { - interceptor.interceptReceivedText(text); - } - onIncomingWebSocketElement(text); } @Override public void onFailure(WebSocket webSocket, Throwable throwable, Response response) { LOGGER.log(Level.FINER, "OkHttp invoked onFailure() for " + webSocket + ". Response: " + response, throwable); - WebSocketException websocketException = new WebSocketException(throwable); - - // If we are already connected, then we need to notify the connection that it got tear down. Otherwise we - // need to notify the thread calling connect() that the connection failed. - if (future.wasSuccessful()) { - connectionInternal.notifyConnectionError(websocketException); - } else { - future.setException(websocketException); - } + onWebSocketFailure(throwable); } @Override @@ -118,16 +81,8 @@ public final class OkHttpWebSocket extends AbstractWebSocket { }; - @Override - public SmackFuture getFuture() { - return future; - } - @Override public void send(String element) { - if (interceptor != null) { - interceptor.interceptSentText(element); - } okHttpWebSocket.send(element); } @@ -137,17 +92,6 @@ public final class OkHttpWebSocket extends AbstractWebSocket { okHttpWebSocket.close(code, message); } - @Override - public boolean isConnectionSecure() { - return endpoint.isSecureEndpoint(); - } - - @Override - public boolean isConnected() { - // TODO: Do we need this method at all if we create an AbstractWebSocket object for every endpoint? - return true; - } - @Override public SSLSession getSSLSession() { // TODO: What shall we do about this method, as it appears that OkHttp does not provide access to the used SSLSession? diff --git a/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocketFactory.java b/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocketFactory.java index 64246a7cc..9b964d5a2 100644 --- a/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocketFactory.java +++ b/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocketFactory.java @@ -23,6 +23,8 @@ import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; public class OkHttpWebSocketFactory implements WebSocketFactory { + public static final OkHttpWebSocketFactory INSTANCE = new OkHttpWebSocketFactory(); + @Override public AbstractWebSocket create(WebSocketRemoteConnectionEndpoint endpoint, ModularXmppClientToServerConnectionInternal connectionInternal) { return new OkHttpWebSocket(endpoint, connectionInternal); diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebSocketConnectionAttemptState.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebSocketConnectionAttemptState.java index 7fb322596..80f58ab42 100644 --- a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebSocketConnectionAttemptState.java +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebSocketConnectionAttemptState.java @@ -24,26 +24,28 @@ import org.jivesoftware.smack.SmackFuture; import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; import org.jivesoftware.smack.fsm.StateTransitionResult; import org.jivesoftware.smack.util.StringUtils; -import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.EstablishingWebSocketConnectionState; import org.jivesoftware.smack.websocket.impl.AbstractWebSocket; -import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService; +import org.jivesoftware.smack.websocket.impl.WebSocketFactory; import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup; public final class WebSocketConnectionAttemptState { + private final ModularXmppClientToServerConnectionInternal connectionInternal; private final XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredEndpoints; + private final WebSocketFactory webSocketFactory; private AbstractWebSocket webSocket; WebSocketConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal, XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredWebSocketEndpoints, - EstablishingWebSocketConnectionState establishingWebSocketConnectionState) { + WebSocketFactory webSocketFactory) { assert discoveredWebSocketEndpoints != null; assert !discoveredWebSocketEndpoints.result.isEmpty(); this.connectionInternal = connectionInternal; this.discoveredEndpoints = discoveredWebSocketEndpoints; + this.webSocketFactory = webSocketFactory; } /** @@ -87,7 +89,7 @@ public final class WebSocketConnectionAttemptState { List webSockets = new ArrayList<>(endpointCount); // First only create the AbstractWebSocket instances, in case a constructor throws. for (WebSocketRemoteConnectionEndpoint endpoint : webSocketEndpoints) { - AbstractWebSocket webSocket = WebSocketFactoryService.createWebSocket(endpoint, connectionInternal); + AbstractWebSocket webSocket = webSocketFactory.create(endpoint, connectionInternal); webSockets.add(webSocket); } diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModule.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModule.java index 0e5f9a5cd..5e2dd5647 100644 --- a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModule.java +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModule.java @@ -50,6 +50,8 @@ import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.XmppWebSock import org.jivesoftware.smack.websocket.elements.WebSocketCloseElement; import org.jivesoftware.smack.websocket.elements.WebSocketOpenElement; import org.jivesoftware.smack.websocket.impl.AbstractWebSocket; +import org.jivesoftware.smack.websocket.impl.WebSocketFactory; +import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService; import org.jivesoftware.smack.websocket.rce.InsecureWebSocketRemoteConnectionEndpoint; import org.jivesoftware.smack.websocket.rce.SecureWebSocketRemoteConnectionEndpoint; import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; @@ -63,6 +65,9 @@ import org.jxmpp.jid.DomainBareJid; */ public final class XmppWebSocketTransportModule extends ModularXmppClientToServerConnectionModule { + + private static final int WEBSOCKET_NORMAL_CLOSURE = 1000; + private final XmppWebSocketTransport websocketTransport; private AbstractWebSocket websocket; @@ -106,8 +111,15 @@ public final class XmppWebSocketTransportModule @Override public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) throws InterruptedException, NoResponseException, NotConnectedException, SmackException, XMPPException { + final WebSocketFactory webSocketFactory; + if (moduleDescriptor.webSocketFactory != null) { + webSocketFactory = moduleDescriptor.webSocketFactory; + } else { + webSocketFactory = WebSocketFactoryService::createWebSocket; + } + WebSocketConnectionAttemptState connectionAttemptState = new WebSocketConnectionAttemptState( - connectionInternal, discoveredWebSocketEndpoints, this); + connectionInternal, discoveredWebSocketEndpoints, webSocketFactory); StateTransitionResult.Failure failure = connectionAttemptState.establishWebSocketConnection(); if (failure != null) { @@ -246,7 +258,7 @@ public final class XmppWebSocketTransportModule @Override protected void disconnect() { - websocket.disconnect(1000, "WebSocket closed normally"); + websocket.disconnect(WEBSOCKET_NORMAL_CLOSURE, "WebSocket closed normally"); } @Override @@ -269,11 +281,6 @@ public final class XmppWebSocketTransportModule return websocket.isConnectionSecure(); } - @Override - public boolean isConnected() { - return websocket.isConnected(); - } - @Override public Stats getStats() { return null; diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModuleDescriptor.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModuleDescriptor.java index b0d02e7c9..e4368dc27 100644 --- a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModuleDescriptor.java +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModuleDescriptor.java @@ -30,6 +30,7 @@ import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionIn import org.jivesoftware.smack.fsm.StateDescriptor; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.EstablishingWebSocketConnectionStateDescriptor; +import org.jivesoftware.smack.websocket.impl.WebSocketFactory; import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; /** @@ -41,14 +42,16 @@ import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor { private final boolean performWebSocketEndpointDiscovery; private final boolean implicitWebSocketEndpoint; - private final URI uri; private final WebSocketRemoteConnectionEndpoint wsRce; + final WebSocketFactory webSocketFactory; + public XmppWebSocketTransportModuleDescriptor(Builder builder) { this.performWebSocketEndpointDiscovery = builder.performWebSocketEndpointDiscovery; this.implicitWebSocketEndpoint = builder.implicitWebSocketEndpoint; + this.webSocketFactory = builder.webSocketFactory; - this.uri = builder.uri; + URI uri = builder.uri; if (uri != null) { wsRce = WebSocketRemoteConnectionEndpoint.from(uri); } else { @@ -95,7 +98,7 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli * @return uri */ public URI getExplicitlyProvidedUri() { - return uri; + return wsRce.getUri(); } WebSocketRemoteConnectionEndpoint getExplicitlyProvidedEndpoint() { @@ -142,6 +145,7 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli private boolean performWebSocketEndpointDiscovery = true; private boolean implicitWebSocketEndpoint = true; private URI uri; + private WebSocketFactory webSocketFactory; private Builder( ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) { @@ -175,6 +179,12 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli return this; } + public Builder setWebSocketFactory(WebSocketFactory webSocketFactory) { + Objects.requireNonNull(webSocketFactory); + this.webSocketFactory = webSocketFactory; + return this; + } + @Override public ModularXmppClientToServerConnectionModuleDescriptor build() { return new XmppWebSocketTransportModuleDescriptor(this); diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/AbstractWebSocket.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/AbstractWebSocket.java index 1c338e733..f0d5b95de 100644 --- a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/AbstractWebSocket.java +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/AbstractWebSocket.java @@ -16,24 +16,44 @@ */ package org.jivesoftware.smack.websocket.impl; +import java.util.logging.Logger; + import javax.net.ssl.SSLSession; import org.jivesoftware.smack.SmackFuture; import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; +import org.jivesoftware.smack.debugger.SmackDebugger; import org.jivesoftware.smack.packet.TopLevelStreamElement; import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.websocket.WebSocketException; import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; public abstract class AbstractWebSocket { + protected static final Logger LOGGER = Logger.getLogger(AbstractWebSocket.class.getName()); + + protected static final String SEC_WEBSOCKET_PROTOCOL_HEADER_FILED_NAME = "Sec-WebSocket-Protocol"; + protected static final String SEC_WEBSOCKET_PROTOCOL_HEADER_FILED_VALUE_XMPP = "xmpp"; + + protected final SmackFuture.InternalSmackFuture future = new SmackFuture.InternalSmackFuture<>(); + protected final ModularXmppClientToServerConnectionInternal connectionInternal; protected final WebSocketRemoteConnectionEndpoint endpoint; + private final SmackWebSocketDebugger debugger; + protected AbstractWebSocket(WebSocketRemoteConnectionEndpoint endpoint, ModularXmppClientToServerConnectionInternal connectionInternal) { this.endpoint = endpoint; this.connectionInternal = connectionInternal; + + final SmackDebugger smackDebugger = connectionInternal.smackDebugger; + if (smackDebugger != null) { + debugger = new SmackWebSocketDebugger(smackDebugger); + } else { + debugger = null; + } } public final WebSocketRemoteConnectionEndpoint getEndpoint() { @@ -44,6 +64,10 @@ public abstract class AbstractWebSocket { private String streamClose; protected final void onIncomingWebSocketElement(String element) { + if (debugger != null) { + debugger.incoming(element); + } + // TODO: Once smack-websocket-java15 is there, we have to re-evaluate if the async operation here is still // required, or if it should only be performed if OkHTTP is used. if (isOpenElement(element)) { @@ -95,11 +119,31 @@ public abstract class AbstractWebSocket { return false; } - public abstract SmackFuture getFuture(); + protected void onWebSocketFailure(Throwable throwable) { + WebSocketException websocketException = new WebSocketException(throwable); + + // If we are already connected, then we need to notify the connection that it got tear down. Otherwise we + // need to notify the thread calling connect() that the connection failed. + if (future.wasSuccessful()) { + connectionInternal.notifyConnectionError(websocketException); + } else { + future.setException(websocketException); + } + } + + public final SmackFuture getFuture() { + return future; + } public final void send(TopLevelStreamElement element) { XmlEnvironment outgoingStreamXmlEnvironment = connectionInternal.getOutgoingStreamXmlEnvironment(); String elementString = element.toXML(outgoingStreamXmlEnvironment).toString(); + + // TODO: We could make use of Java 11's WebSocket (is)last feature when sending + if (debugger != null) { + debugger.outgoing(elementString); + } + send(elementString); } @@ -107,9 +151,9 @@ public abstract class AbstractWebSocket { public abstract void disconnect(int code, String message); - public abstract boolean isConnectionSecure(); + public boolean isConnectionSecure() { + return endpoint.isSecureEndpoint(); + } public abstract SSLSession getSSLSession(); - - public abstract boolean isConnected(); } diff --git a/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/LoggingInterceptor.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/SmackWebSocketDebugger.java similarity index 63% rename from smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/LoggingInterceptor.java rename to smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/SmackWebSocketDebugger.java index 99697617e..6751883f6 100644 --- a/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/LoggingInterceptor.java +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/SmackWebSocketDebugger.java @@ -14,57 +14,42 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jivesoftware.smack.websocket.okhttp; +package org.jivesoftware.smack.websocket.impl; import java.io.IOException; -import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; import org.jivesoftware.smack.debugger.SmackDebugger; -import okhttp3.Headers; -import okhttp3.Response; - import org.jxmpp.xml.splitter.XmlPrettyPrinter; import org.jxmpp.xml.splitter.XmppXmlSplitter; -public final class LoggingInterceptor { - private static final Logger LOGGER = Logger.getLogger(LoggingInterceptor.class.getName()); +public class SmackWebSocketDebugger { + + private static final Logger LOGGER = Logger.getLogger(SmackWebSocketDebugger.class.getName()); private final SmackDebugger debugger; private final XmppXmlSplitter incomingXmlSplitter; private final XmppXmlSplitter outgoingXmlSplitter; - LoggingInterceptor(SmackDebugger smackDebugger) { + SmackWebSocketDebugger(SmackDebugger smackDebugger) { this.debugger = smackDebugger; XmlPrettyPrinter incomingTextPrinter = XmlPrettyPrinter.builder() - .setPrettyWriter(sb -> debugger.incomingStreamSink(sb)) - .setTabWidth(4) - .build(); + .setPrettyWriter(sb -> debugger.incomingStreamSink(sb)) + .setTabWidth(4) + .build(); incomingXmlSplitter = new XmppXmlSplitter(incomingTextPrinter); XmlPrettyPrinter outgoingTextPrinter = XmlPrettyPrinter.builder() - .setPrettyWriter(sb -> debugger.outgoingStreamSink(sb)) - .setTabWidth(4) - .build(); + .setPrettyWriter(sb -> debugger.outgoingStreamSink(sb)) + .setTabWidth(4) + .build(); outgoingXmlSplitter = new XmppXmlSplitter(outgoingTextPrinter); } - // Open response received here isn't in the form of an Xml an so, there isn't much to format. - void interceptOpenResponse(Response response) { - Headers headers = response.headers(); - Iterator iterator = headers.iterator(); - StringBuilder sb = new StringBuilder(); - sb.append("Received headers:"); - while (iterator.hasNext()) { - sb.append("\n\t" + iterator.next()); - } - debugger.incomingStreamSink(sb); - } - - void interceptReceivedText(String text) { + void incoming(String text) { try { incomingXmlSplitter.write(text); } catch (IOException e) { @@ -73,7 +58,7 @@ public final class LoggingInterceptor { } } - void interceptSentText(String text) { + void outgoing(String text) { try { outgoingXmlSplitter.write(text); } catch (IOException e) {