├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs ├── org.eclipse.jdt.core.prefs └── org.eclipse.m2e.core.prefs ├── README.md ├── plugin.xml ├── pom.xml └── src ├── main └── java │ └── io │ └── inventit │ └── dev │ └── mqtt │ └── paho │ ├── MqttWebSocketAsyncClient.java │ ├── PahoConsoleLogger.java │ └── WebSocketNetworkModule.java └── test └── java └── io └── inventit └── dev └── mqtt └── paho ├── JettyStrErrLogUtils.java ├── MqttWebSocketAsyncClientInContainerTest.java ├── MqttWebSocketAsyncClientTest.java └── WebSocketNetworkModuleInContainerTest.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /pom.xml.versionsBackup 3 | .idea 4 | *.iml -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | mqtt-websocket-java 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/main/resources=UTF-8 4 | encoding//src/test/java=UTF-8 5 | encoding//src/test/resources=UTF-8 6 | encoding/=UTF-8 7 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 3 | org.eclipse.jdt.core.compiler.compliance=1.6 4 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 5 | org.eclipse.jdt.core.compiler.source=1.6 6 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Announcement 2 | 3 | Eclipse Paho Java client has WebSocket support. You can use Paho Java client for using MQTT over WebSocket. 4 | 5 | Please refer to the project page at https://eclipse.org/paho/clients/java/ 6 | 7 | The project is EOL. Thank you for all our project users and contributors. 8 | 9 | Daisuke Baba 10 | 11 | --- 12 | 13 | # MQTT over WebSocket library for Java 14 | 15 | This library offers MQTT client functionality over WebSocket transport with [Paho](http://www.eclipse.org/paho/) library and [Jetty](http://www.eclipse.org/jetty/) library. 16 | 17 | # Supported MQTT Version 18 | 19 | 1. MQTT v3.1 (with Sub-Protocol: `mqttv3.1`) 20 | 1. MQTT v3.1.1 (with Sub-Protocol: `mqtt`) ... DEFAULT 21 | 22 | # Supported Paho MQTT library version and Jetty WebSocket Client version 23 | 24 | 1. [Paho org.eclipse.paho.mqtt.java 1.0.2](http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.java.git/tag/?id=v1.0.2) 25 | 1. [Jetty websocket-client 9.2.14.v20151106](http://www.eclipse.org/jetty/documentation/current/jetty-websocket-client-api.html) 26 | 27 | # Supported JDK/JRE Version 28 | 29 | JDK/JRE 1.7+ is required as Jetty no longer supports JDK/JRE 1.6. 30 | 31 | However, [`mqtt-websocket-jdk16-android`](https://github.com/inventit/mqtt-websocket-jdk16-android) library works on JDK/JRE 1.6 environment. 32 | 33 | # HTTP Proxy Unsupported 34 | 35 | Jetty9 WebSocket client doesn't support HTTP proxy. Therefore, this library won't work with HTTP proxy. 36 | 37 | # Dependencies 38 | 39 | The following libraries are requried as well. 40 | 41 | | GroupId | ArtifactId | Version | 42 | |---------------------------|----------------|----------------| 43 | |org.eclipse.jetty |jetty-io |9.2.14.v20151106| 44 | |org.eclipse.jetty |jetty-util |9.2.14.v20151106| 45 | |org.eclipse.jetty.websocket|websocket-api |9.2.14.v20151106| 46 | |org.eclipse.jetty.websocket|websocket-common|9.2.14.v20151106| 47 | 48 | ## maven pom.xml settings 49 | 50 | Adds the following elements to your pom.xml if you're using maven. 51 | 52 | ``` 53 | 54 | io.inventit.dev 55 | mqtt-websocket-java 56 | 1.0.1 57 | 58 | 59 | org.eclipse.jetty.websocket 60 | websocket-client 61 | 9.2.14.v20151106 62 | 63 | 64 | org.eclipse.paho 65 | org.eclipse.paho.client.mqttv3 66 | 1.0.2 67 | 68 | ``` 69 | 70 | # How to install 71 | 72 | ## Downloading the jar file 73 | 74 | curl -O -L https://github.com/inventit/mqtt-websocket-java/releases/download/1.0.1/mqtt-websocket-java-1.0.1.jar 75 | 76 | Or you can create a jar file by yourself (see below). 77 | 78 | ## Installing into your mvn local repository 79 | 80 | mvn install:install-file -Dfile=mqtt-websocket-java-1.0.1.jar \ 81 | -DgroupId=io.inventit.dev -DartifactId=mqtt-websocket-java \ 82 | -Dversion=1.0.1 -Dpackaging=jar 83 | 84 | # How to use 85 | You can use this library as the same manner as Paho's library but use `MqttWebSocketAsyncClient` instead of Paho's classes such as `MqttClient` and `MqttAsyncClient`. 86 | 87 | The `MqttWebSocketAsyncClient` supports the following URI schimes: 88 | 89 | 1. `ws://:` ... for a plain WebSocket 90 | 1. `wss://:` ... for a WebSocket with SSL/TLS 91 | 1. `tcp://:` ... for a plain TCP MQTT socket 92 | 1. `ssl://:` ... for a secure SSL/TLS MQTT socket 93 | 94 | Here is sample code to use `MqttWebSocketAsyncClient`. 95 | 96 | // Plain MQTT 97 | // final String uriString = "tcp://your-mqtt-broker:1883"; 98 | 99 | // MQTT over WebSocket 100 | final String uriString = "wss://your-ws-broker/mqtt"; 101 | 102 | // Credentials 103 | final String clientId = "your-client-id"; 104 | final String userName = "your-user-name"; 105 | final String password = "your-password"; 106 | 107 | final IMqttAsyncClient client = new MqttWebSocketAsyncClient( 108 | uriString, clientId, new MemoryPersistence()); 109 | final MqttConnectOptions options = new MqttConnectOptions(); 110 | options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1); 111 | options.setCleanSession(true); 112 | options.setUserName(userName); 113 | options.setPassword(password.toCharArray()); 114 | client.connect(options); 115 | client.setCallback(new MqttCallback() { 116 | 117 | @Override 118 | public void messageArrived(String topic, MqttMessage message) 119 | throws Exception { 120 | } 121 | 122 | @Override 123 | public void deliveryComplete(IMqttDeliveryToken token) { 124 | } 125 | 126 | @Override 127 | public void connectionLost(Throwable cause) { 128 | } 129 | }); 130 | 131 | # How to build 132 | 133 | Install maven then run the following command on the project root directory. 134 | 135 | $ mvn clean package 136 | 137 | Then you'll get `mqtt-websocket-java-.jar` under the `target` directory. 138 | 139 | # Source Code License 140 | 141 | All program source codes are available under the MIT style License. 142 | 143 | Copyright (c) 2014 Inventit Inc. 144 | 145 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 146 | 147 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 148 | 149 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 150 | 151 | # Change History 152 | 153 | [1.0.1 : November 29, 2014](https://github.com/inventit/mqtt-websocket-java/releases/tag/1.0.1) 154 | 155 | * Upgrades Jetty 9 library 156 | * Adds a new factory method for WebSocketNetworkModule instance 157 | * Adds new constructors with a new paramter for specifying the logger name to MqttWebSocketAsyncClient 158 | * Releases a JDK1.6 class version (50) jar as well in order for Android app to include this library (**Note that Jetty 9 itself doesn't support Android and JDK 1.6**) 159 | 160 | [1.0.0 : July 30, 2014](https://github.com/inventit/mqtt-websocket-java/releases/tag/1.0.0) 161 | 162 | * Initial 163 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | mqtt-websocket-java 7 | MQTT over WebScoket for Java with Paho and Jetty 8 | MIT 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | 7 | io.inventit.dev 8 | mqtt-websocket-java 9 | 1.0.1 10 | MQTT over WebScoket for Java with Paho and Jetty 11 | https://github.com/inventit/mqtt-websocket-java 12 | 13 | 14 | MIT 15 | https://github.com/inventit/mqtt-websocket-java/blob/master/README.md#source-code-license 16 | 17 | 18 | 19 | https://github.com/inventit/mqtt-websocket-java 20 | scm:git:git://github.com/inventit/mqtt-websocket-java.git 21 | scm:git:git@github.com:inventit/mqtt-websocket-java.git 22 | 23 | 24 | https://github.com/inventit/mqtt-websocket-java/issues 25 | GitHub Issues 26 | 27 | 28 | 29 | 30 | 1.7 31 | UTF-8 32 | 33 | 34 | 35 | 36 | 37 | maven-compiler-plugin 38 | 3.1 39 | 40 | UTF-8 41 | 1.6 42 | ${target.jdk} 43 | 44 | 45 | 46 | maven-surefire-plugin 47 | 2.17 48 | 49 | 0 50 | -Dfile.encoding=UTF-8 -Xmx512m -XX:MaxPermSize=256m 51 | 52 | **/*InContainerTest.java 53 | 54 | 55 | 56 | 57 | org.codehaus.mojo 58 | cobertura-maven-plugin 59 | 2.5.1 60 | 61 | 62 | false 63 | 64 | 65 | 66 | 67 | pre-site 68 | 69 | clean 70 | check 71 | 72 | 73 | 74 | 75 | 76 | maven-clean-plugin 77 | 78 | 79 | 80 | ${basedir} 81 | 82 | **/*.log* 83 | 84 | 85 | .hg/**/* 86 | .git/**/* 87 | **/src/test/resources/logs/* 88 | 89 | false 90 | 91 | 92 | 93 | 94 | 95 | maven-javadoc-plugin 96 | 2.9 97 | 98 | 99 | 100 | UTF-8 101 | 102 | http://java.sun.com/javase/6/docs/api/ 103 | http://download.oracle.com/javaee/5/api/ 104 | 105 | 106 | 107 | 108 | maven-jar-plugin 109 | 110 | ${jdk.classifier} 111 | 112 | false 113 | 114 | true 115 | 116 | 117 | ${buildNumber} 118 | InventIt Inc. dev.inventit.io 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | org.eclipse.jetty.websocket 129 | websocket-client 130 | 9.2.14.v20151106 131 | 132 | 133 | org.eclipse.paho 134 | org.eclipse.paho.client.mqttv3 135 | 1.0.2 136 | 137 | 138 | junit 139 | junit 140 | 4.11 141 | test 142 | 143 | 144 | org.mockito 145 | mockito-all 146 | 1.9.5 147 | test 148 | 149 | 150 | org.easytesting 151 | fest-assert-core 152 | 2.0M10 153 | test 154 | 155 | 156 | org.jmock 157 | jmock 158 | 2.6.0 159 | test 160 | 161 | 162 | 163 | 164 | jdk16 165 | 166 | 1.6 167 | 168 | 169 | jdk16 170 | 1.6 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /src/main/java/io/inventit/dev/mqtt/paho/MqttWebSocketAsyncClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Inventit Inc. 3 | */ 4 | package io.inventit.dev.mqtt.paho; 5 | 6 | import java.net.URI; 7 | 8 | import org.eclipse.paho.client.mqttv3.MqttAsyncClient; 9 | import org.eclipse.paho.client.mqttv3.MqttClientPersistence; 10 | import org.eclipse.paho.client.mqttv3.MqttConnectOptions; 11 | import org.eclipse.paho.client.mqttv3.MqttException; 12 | import org.eclipse.paho.client.mqttv3.MqttPingSender; 13 | import org.eclipse.paho.client.mqttv3.MqttSecurityException; 14 | import org.eclipse.paho.client.mqttv3.TimerPingSender; 15 | import org.eclipse.paho.client.mqttv3.internal.NetworkModule; 16 | import org.eclipse.paho.client.mqttv3.logging.Logger; 17 | import org.eclipse.paho.client.mqttv3.logging.LoggerFactory; 18 | import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence; 19 | 20 | public class MqttWebSocketAsyncClient extends MqttAsyncClient { 21 | 22 | private static final String CLASS_NAME = MqttWebSocketAsyncClient.class 23 | .getName(); 24 | private final Logger log; 25 | 26 | private final String serverURI; 27 | 28 | /** 29 | * Create a dummy URI in order to by-pass MqttConnectOptions.validateURI() 30 | * validation. 31 | * 32 | * @param original 33 | * @return 34 | */ 35 | protected static String createDummyURI(String original) { 36 | if (!original.startsWith("ws:") && !original.startsWith("wss:")) { 37 | return original; 38 | } 39 | final URI uri = URI.create(original); 40 | return "tcp://DUMMY-" + uri.getHost() + ":" 41 | + (uri.getPort() > 0 ? uri.getPort() : 80); 42 | } 43 | 44 | protected static boolean isDummyURI(String uri) { 45 | return uri.startsWith("tcp://DUMMY-"); 46 | } 47 | 48 | public MqttWebSocketAsyncClient(String serverURI, String clientId, 49 | MqttClientPersistence persistence, MqttPingSender pingSender, 50 | String loggerName) throws MqttException { 51 | 52 | super(createDummyURI(serverURI), clientId, persistence, pingSender); 53 | this.serverURI = serverURI; 54 | 55 | final String methodName = "MqttWebSocketAsyncClient"; 56 | 57 | this.log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, 58 | (loggerName == null || loggerName.length() == 0) ? CLASS_NAME 59 | : loggerName); 60 | 61 | // @TRACE 101= ClientID={0} ServerURI={1} PersistenceType={2} 62 | if (log.isLoggable(Logger.FINE)) { 63 | log.fine(CLASS_NAME, methodName, "101", new Object[] { clientId, 64 | serverURI, persistence }); 65 | } 66 | } 67 | 68 | public MqttWebSocketAsyncClient(String serverURI, String clientId, 69 | MqttClientPersistence persistence, String loggerName) 70 | throws MqttException { 71 | this(serverURI, clientId, persistence, new TimerPingSender(), 72 | loggerName); 73 | } 74 | 75 | public MqttWebSocketAsyncClient(String serverURI, String clientId, 76 | MqttClientPersistence persistence) throws MqttException { 77 | this(serverURI, clientId, persistence, null); 78 | } 79 | 80 | public MqttWebSocketAsyncClient(String serverURI, String clientId, 81 | String loggerName) throws MqttException { 82 | this(serverURI, clientId, new MqttDefaultFilePersistence(), loggerName); 83 | } 84 | 85 | public MqttWebSocketAsyncClient(String serverURI, String clientId) 86 | throws MqttException { 87 | this(serverURI, clientId, (String) null); 88 | } 89 | 90 | /** 91 | * Same as super{@link #createNetworkModules(String, MqttConnectOptions)} 92 | */ 93 | @Override 94 | protected NetworkModule[] createNetworkModules(String address, 95 | MqttConnectOptions options) throws MqttException, 96 | MqttSecurityException { 97 | final String methodName = "createNetworkModules"; 98 | // @TRACE 116=URI={0} 99 | if (log.isLoggable(Logger.FINE)) { 100 | log.fine(CLASS_NAME, methodName, "116", new Object[] { address }); 101 | } 102 | NetworkModule[] networkModules = null; 103 | String[] serverURIs = options.getServerURIs(); 104 | String[] array = null; 105 | if (serverURIs == null) { 106 | array = new String[] { address }; 107 | } else if (serverURIs.length == 0) { 108 | array = new String[] { address }; 109 | } else { 110 | array = serverURIs; 111 | } 112 | 113 | networkModules = new NetworkModule[array.length]; 114 | for (int i = 0; i < array.length; i++) { 115 | networkModules[i] = createNetworkModule(array[i], options); 116 | } 117 | 118 | log.fine(CLASS_NAME, methodName, "108"); 119 | return networkModules; 120 | } 121 | 122 | /** 123 | * Factory method to create the correct network module, based on the 124 | * supplied address URI. 125 | * 126 | * @param address 127 | * the URI for the server. 128 | * @param options 129 | * MQTT connect options 130 | * @return a network module appropriate to the specified address. 131 | */ 132 | protected NetworkModule createNetworkModule(String input, 133 | MqttConnectOptions options) throws MqttException, 134 | MqttSecurityException { 135 | final String address = isDummyURI(input) ? this.serverURI : input; 136 | if (!address.startsWith("ws:") && !address.startsWith("wss:")) { 137 | return super.createNetworkModules(address, options)[0]; 138 | } 139 | 140 | final String methodName = "createNetworkModule"; 141 | // @TRACE 115=URI={0} 142 | if (log.isLoggable(Logger.FINE)) { 143 | log.fine(CLASS_NAME, methodName, "115", new Object[] { address }); 144 | } 145 | 146 | final String subProtocol; 147 | if (options.getMqttVersion() == MqttConnectOptions.MQTT_VERSION_3_1) { 148 | // http://wiki.eclipse.org/Paho/Paho_Websockets#Ensuring_implementations_can_inter-operate 149 | subProtocol = "mqttv3.1"; 150 | } else { 151 | // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/cs01/mqtt-v3.1.1-cs01.html#_Toc388534418 152 | subProtocol = "mqtt"; 153 | } 154 | return newWebSocketNetworkModule(URI.create(address), subProtocol, 155 | options); 156 | } 157 | 158 | /** 159 | * A factory method for instantiating a {@link NetworkModule} with websocket 160 | * support. Subclasses is able to extend this method in order to create an 161 | * arbitrary {@link NetworkModule} class instance. 162 | * 163 | * @param uri 164 | * @param subProtocol 165 | * Either `mqtt` for MQTT v3 or `mqttv3.1` for MQTT v3.1 166 | * @param options 167 | * @return 168 | */ 169 | protected NetworkModule newWebSocketNetworkModule(URI uri, 170 | String subProtocol, MqttConnectOptions options) { 171 | final WebSocketNetworkModule netModule = new WebSocketNetworkModule( 172 | uri, subProtocol, getClientId()); 173 | netModule.setConnectTimeout(options.getConnectionTimeout()); 174 | return netModule; 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/io/inventit/dev/mqtt/paho/PahoConsoleLogger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Inventit Inc. 3 | */ 4 | package io.inventit.dev.mqtt.paho; 5 | 6 | import java.text.MessageFormat; 7 | import java.util.ResourceBundle; 8 | 9 | import org.eclipse.paho.client.mqttv3.logging.Logger; 10 | import org.eclipse.paho.client.mqttv3.logging.LoggerFactory; 11 | 12 | public class PahoConsoleLogger implements Logger { 13 | 14 | public static void enableLog() { 15 | LoggerFactory.setLogger(PahoConsoleLogger.class.getName()); 16 | } 17 | 18 | private ResourceBundle messageCatalog; 19 | 20 | @Override 21 | public void initialise(ResourceBundle messageCatalog, String loggerID, 22 | String resourceName) { 23 | this.messageCatalog = messageCatalog; 24 | } 25 | 26 | @Override 27 | public void setResourceName(String logContext) { 28 | } 29 | 30 | @Override 31 | public boolean isLoggable(int level) { 32 | return true; 33 | } 34 | 35 | @Override 36 | public void severe(String sourceClass, String sourceMethod, String msg) { 37 | System.err.println("[SEVERE]" + sourceClass + ":" + sourceMethod + ":" 38 | + msg); 39 | } 40 | 41 | @Override 42 | public void severe(String sourceClass, String sourceMethod, String msg, 43 | Object[] inserts) { 44 | System.err.println("[SEVERE]" + sourceClass + ":" + sourceMethod + ":" 45 | + formatMessage(msg, inserts)); 46 | } 47 | 48 | @Override 49 | public void severe(String sourceClass, String sourceMethod, String msg, 50 | Object[] inserts, Throwable thrown) { 51 | System.err.println("[SEVERE]" + sourceClass + ":" + sourceMethod + ":" 52 | + formatMessage(msg, inserts)); 53 | thrown.printStackTrace(); 54 | } 55 | 56 | @Override 57 | public void warning(String sourceClass, String sourceMethod, String msg) { 58 | System.err.println("[WARN]" + sourceClass + ":" + sourceMethod); 59 | } 60 | 61 | @Override 62 | public void warning(String sourceClass, String sourceMethod, String msg, 63 | Object[] inserts) { 64 | System.err.println("[WARN]" + sourceClass + ":" + sourceMethod + ":" 65 | + formatMessage(msg, inserts)); 66 | } 67 | 68 | @Override 69 | public void warning(String sourceClass, String sourceMethod, String msg, 70 | Object[] inserts, Throwable thrown) { 71 | System.err.println("[WARN]" + sourceClass + ":" + sourceMethod + ":" 72 | + formatMessage(msg, inserts)); 73 | thrown.printStackTrace(); 74 | } 75 | 76 | @Override 77 | public void info(String sourceClass, String sourceMethod, String msg) { 78 | System.err.println("[INFO]" + sourceClass + ":" + sourceMethod + ":" 79 | + msg); 80 | } 81 | 82 | @Override 83 | public void info(String sourceClass, String sourceMethod, String msg, 84 | Object[] inserts) { 85 | System.err.println("[INFO]" + sourceClass + ":" + sourceMethod + ":" 86 | + formatMessage(msg, inserts)); 87 | } 88 | 89 | @Override 90 | public void info(String sourceClass, String sourceMethod, String msg, 91 | Object[] inserts, Throwable thrown) { 92 | System.err.println("[INFO]" + sourceClass + ":" + sourceMethod + ":" 93 | + formatMessage(msg, inserts)); 94 | thrown.printStackTrace(); 95 | } 96 | 97 | @Override 98 | public void config(String sourceClass, String sourceMethod, String msg) { 99 | System.err.println("[CONFIG]" + sourceClass + ":" + sourceMethod + ":" 100 | + msg); 101 | } 102 | 103 | @Override 104 | public void config(String sourceClass, String sourceMethod, String msg, 105 | Object[] inserts) { 106 | System.err.println("[CONFIG]" + sourceClass + ":" + sourceMethod + ":" 107 | + formatMessage(msg, inserts)); 108 | } 109 | 110 | @Override 111 | public void config(String sourceClass, String sourceMethod, String msg, 112 | Object[] inserts, Throwable thrown) { 113 | System.err.println("[CONFIG]" + sourceClass + ":" + sourceMethod + ":" 114 | + formatMessage(msg, inserts)); 115 | thrown.printStackTrace(); 116 | } 117 | 118 | @Override 119 | public void fine(String sourceClass, String sourceMethod, String msg) { 120 | System.err.println("[FINE]" + sourceClass + ":" + sourceMethod + ":" 121 | + msg); 122 | } 123 | 124 | @Override 125 | public void fine(String sourceClass, String sourceMethod, String msg, 126 | Object[] inserts) { 127 | System.err.println("[FINE]" + sourceClass + ":" + sourceMethod + ":" 128 | + formatMessage(msg, inserts)); 129 | } 130 | 131 | @Override 132 | public void fine(String sourceClass, String sourceMethod, String msg, 133 | Object[] inserts, Throwable ex) { 134 | System.err.println("[FINE]" + sourceClass + ":" + sourceMethod + ":" 135 | + formatMessage(msg, inserts)); 136 | ex.printStackTrace(); 137 | } 138 | 139 | @Override 140 | public void finer(String sourceClass, String sourceMethod, String msg) { 141 | System.err.println("[FINER]" + sourceClass + ":" + sourceMethod + ":" 142 | + msg); 143 | } 144 | 145 | @Override 146 | public void finer(String sourceClass, String sourceMethod, String msg, 147 | Object[] inserts) { 148 | System.err.println("[FINER]" + sourceClass + ":" + sourceMethod + ":" 149 | + formatMessage(msg, inserts)); 150 | } 151 | 152 | @Override 153 | public void finer(String sourceClass, String sourceMethod, String msg, 154 | Object[] inserts, Throwable ex) { 155 | System.err.println("[FINER]" + sourceClass + ":" + sourceMethod + ":" 156 | + formatMessage(msg, inserts)); 157 | ex.printStackTrace(); 158 | } 159 | 160 | @Override 161 | public void finest(String sourceClass, String sourceMethod, String msg) { 162 | System.err.println("[FINEST]" + sourceClass + ":" + sourceMethod + ":" 163 | + msg); 164 | } 165 | 166 | @Override 167 | public void finest(String sourceClass, String sourceMethod, String msg, 168 | Object[] inserts) { 169 | System.err.println("[FINEST]" + sourceClass + ":" + sourceMethod + ":" 170 | + formatMessage(msg, inserts)); 171 | } 172 | 173 | @Override 174 | public void finest(String sourceClass, String sourceMethod, String msg, 175 | Object[] inserts, Throwable ex) { 176 | System.err.println("[FINEST]" + sourceClass + ":" + sourceMethod + ":" 177 | + formatMessage(msg, inserts)); 178 | ex.printStackTrace(); 179 | } 180 | 181 | @Override 182 | public void log(int level, String sourceClass, String sourceMethod, 183 | String msg, Object[] inserts, Throwable thrown) { 184 | switch (level) { 185 | case Logger.CONFIG: 186 | config(sourceClass, sourceMethod, msg, inserts, thrown); 187 | break; 188 | default: 189 | case Logger.FINE: 190 | fine(sourceClass, sourceMethod, msg, inserts, thrown); 191 | break; 192 | case Logger.FINER: 193 | finer(sourceClass, sourceMethod, msg, inserts, thrown); 194 | break; 195 | case Logger.FINEST: 196 | finest(sourceClass, sourceMethod, msg, inserts, thrown); 197 | break; 198 | case Logger.INFO: 199 | info(sourceClass, sourceMethod, msg, inserts, thrown); 200 | break; 201 | case Logger.WARNING: 202 | warning(sourceClass, sourceMethod, msg, inserts, thrown); 203 | break; 204 | case Logger.SEVERE: 205 | severe(sourceClass, sourceMethod, msg, inserts, thrown); 206 | break; 207 | } 208 | } 209 | 210 | @Override 211 | public void trace(int level, String sourceClass, String sourceMethod, 212 | String msg, Object[] inserts, Throwable thrown) { 213 | log(level, sourceClass, sourceMethod, msg, inserts, thrown); 214 | } 215 | 216 | @Override 217 | public String formatMessage(String msg, Object[] inserts) { 218 | if (!messageCatalog.containsKey(msg)) { 219 | return MessageFormat.format(msg, inserts); 220 | } 221 | final String message = messageCatalog.getString(msg); 222 | if (inserts == null) { 223 | return message; 224 | } else { 225 | return MessageFormat.format(message, inserts); 226 | } 227 | } 228 | 229 | @Override 230 | public void dumpTrace() { 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /src/main/java/io/inventit/dev/mqtt/paho/WebSocketNetworkModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Inventit Inc. 3 | */ 4 | package io.inventit.dev.mqtt.paho; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.io.PipedInputStream; 11 | import java.io.PipedOutputStream; 12 | import java.net.ConnectException; 13 | import java.net.URI; 14 | import java.nio.ByteBuffer; 15 | import java.util.concurrent.Future; 16 | 17 | import javax.net.ssl.SSLEngine; 18 | import javax.net.ssl.SSLParameters; 19 | 20 | import org.eclipse.jetty.util.ssl.SslContextFactory; 21 | import org.eclipse.jetty.websocket.api.Session; 22 | import org.eclipse.jetty.websocket.api.WebSocketAdapter; 23 | import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; 24 | import org.eclipse.jetty.websocket.client.WebSocketClient; 25 | import org.eclipse.paho.client.mqttv3.MqttException; 26 | import org.eclipse.paho.client.mqttv3.internal.NetworkModule; 27 | import org.eclipse.paho.client.mqttv3.logging.Logger; 28 | import org.eclipse.paho.client.mqttv3.logging.LoggerFactory; 29 | 30 | /** 31 | * A network module for connecting over WebScoket with Jetty 9. 32 | */ 33 | public class WebSocketNetworkModule extends WebSocketAdapter implements 34 | NetworkModule { 35 | private static final String CLASS_NAME = WebSocketNetworkModule.class 36 | .getName(); 37 | private static final Logger log = LoggerFactory.getLogger( 38 | LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME); 39 | 40 | /** 41 | * WebSocket URI 42 | */ 43 | private final URI uri; 44 | 45 | /** 46 | * Sub-Protocol 47 | */ 48 | private final String subProtocol; 49 | 50 | /** 51 | * A stream for outgoing data 52 | */ 53 | private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream() { 54 | @Override 55 | public void flush() throws IOException { 56 | final ByteBuffer byteBuffer; 57 | synchronized (this) { 58 | byteBuffer = ByteBuffer.wrap(toByteArray()); 59 | reset(); 60 | } 61 | // Asynchronous call 62 | getRemote().sendBytes(byteBuffer); 63 | getRemote().flush(); 64 | } 65 | }; 66 | 67 | /** 68 | * A pair of streams for incoming data 69 | */ 70 | private final PipedOutputStream receiverStream = new PipedOutputStream(); 71 | private final PipedInputStream inputStream; 72 | 73 | private WebSocketClient client; 74 | private int conTimeout; 75 | 76 | /** 77 | * Constructs a new WebSocketNetworkModule using the specified URI. 78 | * 79 | * @param uri 80 | * @param subProtocol 81 | * @param resourceContext 82 | */ 83 | public WebSocketNetworkModule(URI uri, String subProtocol, 84 | String resourceContext) { 85 | log.setResourceName(resourceContext); 86 | this.uri = uri; 87 | this.subProtocol = subProtocol; 88 | try { 89 | this.inputStream = new PipedInputStream(receiverStream); 90 | } catch (IOException unexpected) { 91 | throw new IllegalStateException(unexpected); 92 | } 93 | } 94 | 95 | /** 96 | * A factory method for {@link ClientUpgradeRequest} class 97 | * 98 | * @return 99 | */ 100 | protected ClientUpgradeRequest createClientUpgradeRequest() { 101 | final ClientUpgradeRequest request = new ClientUpgradeRequest(); 102 | // you can manipulate the request by overriding this method. 103 | return request; 104 | } 105 | 106 | /** 107 | * A factory method for {@link WebSocketClient} class 108 | * 109 | * @return 110 | */ 111 | protected WebSocketClient createWebSocketClient() { 112 | final WebSocketClient client = new WebSocketClient( 113 | createSslContextFactory()); 114 | // you can manipulate the client by overriding this method. 115 | return client; 116 | } 117 | 118 | /** 119 | * A factory method for {@link SslContextFactory} class, used for 120 | * instantiating a WebSocketClient() 121 | * 122 | * @return 123 | */ 124 | protected SslContextFactory createSslContextFactory() { 125 | return new SslContextFactory(true){ 126 | @Override 127 | public void customize(SSLEngine sslEngine) { 128 | SSLParameters sslParams = sslEngine.getSSLParameters(); 129 | //sslParams.setEndpointIdentificationAlgorithm(_endpointIdentificationAlgorithm); 130 | sslEngine.setSSLParameters(sslParams); 131 | 132 | if (getWantClientAuth()) 133 | sslEngine.setWantClientAuth(getWantClientAuth()); 134 | if (getNeedClientAuth()) 135 | sslEngine.setNeedClientAuth(getNeedClientAuth()); 136 | 137 | sslEngine.setEnabledCipherSuites(selectCipherSuites( 138 | sslEngine.getEnabledCipherSuites(), 139 | sslEngine.getSupportedCipherSuites())); 140 | 141 | sslEngine.setEnabledProtocols(selectProtocols(sslEngine.getEnabledProtocols(),sslEngine.getSupportedProtocols())); 142 | } 143 | }; 144 | } 145 | 146 | /** 147 | * Starts the module, by creating a TCP socket to the server. 148 | */ 149 | @Override 150 | public void start() throws IOException, MqttException { 151 | final String methodName = "start"; 152 | try { 153 | // @TRACE 252=connect to host {0} port {1} timeout {2} 154 | if (log.isLoggable(Logger.FINE)) { 155 | log.fine( 156 | CLASS_NAME, 157 | methodName, 158 | "252", 159 | new Object[] { uri.toString(), 160 | Integer.valueOf(uri.getPort()), 161 | Long.valueOf(conTimeout * 1000) }); 162 | } 163 | client = createWebSocketClient(); 164 | client.setConnectTimeout(conTimeout * 1000); 165 | if (client.isStarted() == false) { 166 | client.start(); 167 | } 168 | 169 | final ClientUpgradeRequest request = createClientUpgradeRequest(); 170 | request.setSubProtocols(subProtocol); 171 | final Future future = client.connect(this, uri, request); 172 | // Replays the same behavior as Socket.connect(). 173 | // blocks until the connection is established or some error occurs. 174 | future.get(); 175 | 176 | } catch (ConnectException ex) { 177 | // @TRACE 250=Failed to create TCP socket 178 | log.fine(CLASS_NAME, methodName, "250", null, ex); 179 | throw new MqttException( 180 | MqttException.REASON_CODE_SERVER_CONNECT_ERROR, ex); 181 | 182 | } catch (Exception ex) { 183 | // @TRACE 250=Failed to create TCP socket 184 | log.fine(CLASS_NAME, methodName, "250", null, ex); 185 | throw new MqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR, 186 | ex); 187 | } 188 | } 189 | 190 | @Override 191 | public InputStream getInputStream() throws IOException { 192 | return inputStream; 193 | } 194 | 195 | @Override 196 | public OutputStream getOutputStream() throws IOException { 197 | return outputStream; 198 | } 199 | 200 | /** 201 | * Stops the module, by closing the web socket. 202 | */ 203 | @Override 204 | public void stop() throws IOException { 205 | try { 206 | client.stop(); 207 | } catch (IOException e) { 208 | throw e; 209 | } catch (Exception e) { 210 | throw new IllegalStateException(e); 211 | } 212 | } 213 | 214 | /** 215 | * Set the maximum time in seconds to wait for a socket to be established 216 | * 217 | * @param timeout 218 | * in seconds 219 | */ 220 | public void setConnectTimeout(int timeout) { 221 | this.conTimeout = timeout; 222 | } 223 | 224 | @Override 225 | public void onWebSocketBinary(byte[] payload, int offset, int len) { 226 | try { 227 | this.receiverStream.write(payload, offset, len); 228 | this.receiverStream.flush(); 229 | } catch (IOException e) { 230 | log.fine(CLASS_NAME, "onWebSocketError", "401", null, e); 231 | throw new IllegalStateException(e); 232 | } 233 | } 234 | 235 | @Override 236 | public void onWebSocketError(Throwable cause) { 237 | if (log.isLoggable(Logger.FINE)) { 238 | log.fine(CLASS_NAME, "onWebSocketError", "401", null, cause); 239 | } 240 | } 241 | 242 | @Override 243 | public void onWebSocketConnect(Session sess) { 244 | super.onWebSocketConnect(sess); 245 | if (log.isLoggable(Logger.FINE)) { 246 | log.fine(CLASS_NAME, "onWebSocketConnect", "116", 247 | new Object[] { uri.toString() + ", WebSocket CONNECTED." }); 248 | } 249 | } 250 | 251 | @Override 252 | public void onWebSocketClose(int statusCode, String reason) { 253 | super.onWebSocketClose(statusCode, reason); 254 | if (log.isLoggable(Logger.FINE)) { 255 | log.fine(CLASS_NAME, "onWebSocketConnect", "116", 256 | new Object[] { uri.toString() + ", WebSocket CLOSED." }); 257 | } 258 | } 259 | 260 | } 261 | -------------------------------------------------------------------------------- /src/test/java/io/inventit/dev/mqtt/paho/JettyStrErrLogUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Inventit Inc. 3 | */ 4 | package io.inventit.dev.mqtt.paho; 5 | 6 | public class JettyStrErrLogUtils { 7 | 8 | private JettyStrErrLogUtils() { 9 | } 10 | 11 | public static void enableLog() { 12 | System.setProperty("org.eclipse.jetty.util.log.class", 13 | "org.eclipse.jetty.util.log.StrErrLog"); 14 | System.setProperty("org.eclipse.jetty.LEVEL", "INFO"); 15 | System.setProperty("org.eclipse.jetty.websocket.LEVEL", "DEBUG"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/io/inventit/dev/mqtt/paho/MqttWebSocketAsyncClientInContainerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Inventit Inc. 3 | */ 4 | package io.inventit.dev.mqtt.paho; 5 | 6 | import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; 7 | import org.eclipse.paho.client.mqttv3.IMqttToken; 8 | import org.eclipse.paho.client.mqttv3.MqttCallback; 9 | import org.eclipse.paho.client.mqttv3.MqttConnectOptions; 10 | import org.eclipse.paho.client.mqttv3.MqttMessage; 11 | import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | 15 | /** 16 | * This test is skipped when building with maven. 17 | */ 18 | public class MqttWebSocketAsyncClientInContainerTest { 19 | 20 | @Before 21 | public void setUp() throws Exception { 22 | JettyStrErrLogUtils.enableLog(); 23 | PahoConsoleLogger.enableLog(); 24 | } 25 | 26 | @Test 27 | public void test() throws Exception { 28 | // Plain MQTT 29 | // final String uriString = "tcp://your-mqtt-broker:1883"; 30 | 31 | // MQTT over WebSocket 32 | final String uriString = "wss://your-ws-broker/mqtt"; 33 | 34 | // Credentials 35 | final String clientId = "123"; 36 | final String userName = ""; 37 | final String password = ""; 38 | 39 | final MqttWebSocketAsyncClient client = new MqttWebSocketAsyncClient( 40 | uriString, clientId, new MemoryPersistence()); 41 | final MqttConnectOptions options = new MqttConnectOptions(); 42 | options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1); 43 | options.setCleanSession(true); 44 | options.setUserName(userName); 45 | options.setPassword(password.toCharArray()); 46 | final IMqttToken token = client.connect(options); 47 | client.setCallback(new MqttCallback() { 48 | 49 | @Override 50 | public void messageArrived(String topic, MqttMessage message) 51 | throws Exception { 52 | System.out.println("messageArrived:" + topic + "/" + message); 53 | } 54 | 55 | @Override 56 | public void deliveryComplete(IMqttDeliveryToken token) { 57 | System.out.println("deliveryComplete:" + token); 58 | } 59 | 60 | @Override 61 | public void connectionLost(Throwable cause) { 62 | System.out.println("connectionLost"); 63 | cause.printStackTrace(); 64 | } 65 | }); 66 | 67 | try { 68 | final long start = System.currentTimeMillis(); 69 | while (client.isConnected() == false 70 | && System.currentTimeMillis() - start < 5000) { 71 | Thread.sleep(100); 72 | } 73 | System.out.println("OK!"); 74 | } finally { 75 | System.out.println(token.isComplete()); 76 | client.disconnect(); 77 | client.close(); 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/io/inventit/dev/mqtt/paho/MqttWebSocketAsyncClientTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Inventit Inc. 3 | */ 4 | package io.inventit.dev.mqtt.paho; 5 | 6 | import static org.junit.Assert.*; 7 | 8 | import java.net.URI; 9 | 10 | import org.junit.Test; 11 | 12 | public class MqttWebSocketAsyncClientTest { 13 | 14 | @Test 15 | public void test_createDummyURI() { 16 | final URI uri = URI.create(MqttWebSocketAsyncClient 17 | .createDummyURI("ws://server.address:123/path")); 18 | assertEquals("tcp", uri.getScheme()); 19 | assertEquals("DUMMY-server.address", uri.getHost()); 20 | assertEquals(123, uri.getPort()); 21 | assertEquals("", uri.getPath()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/io/inventit/dev/mqtt/paho/WebSocketNetworkModuleInContainerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Inventit Inc. 3 | */ 4 | package io.inventit.dev.mqtt.paho; 5 | 6 | import java.net.URI; 7 | 8 | import org.junit.Test; 9 | 10 | public class WebSocketNetworkModuleInContainerTest { 11 | 12 | @Test 13 | public void test_connection() throws Exception { 14 | final String uriString = "ws://your.broker.com:80/mqtt"; 15 | final String clientId = "my-client-id"; 16 | 17 | final WebSocketNetworkModule module = new WebSocketNetworkModule( 18 | URI.create(uriString), "mqttv3.1", clientId); 19 | module.start(); 20 | System.out.println("OK!"); 21 | 22 | module.getOutputStream().write("test".getBytes()); 23 | module.getOutputStream().flush(); 24 | module.stop(); 25 | System.out.println("END"); 26 | } 27 | 28 | } 29 | --------------------------------------------------------------------------------