├── .gitignore ├── README.md ├── docker-compose.yml ├── justfile ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── alibaba │ │ └── rsocket │ │ └── mqtt │ │ └── gateway │ │ ├── MqttAutoConfiguration.java │ │ ├── MqttHandler.java │ │ ├── PublishService.java │ │ ├── RSocketBrokerGatewayMqttServer.java │ │ ├── RSocketController.java │ │ ├── Subscription.java │ │ ├── SubscriptionManager.java │ │ ├── TopicStore.java │ │ └── impl │ │ ├── PublishServiceImpl.java │ │ ├── SimpleTopicStore.java │ │ └── SubscriptionManagerImpl.java └── resources │ └── application.properties └── test └── java └── com └── alibaba └── rsocket └── mqtt └── gateway ├── SpringBootBaseTestCase.java └── impl └── SimpleTopicStoreTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/java,maven,jetbrains+all 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=java,maven,jetbrains+all 4 | 5 | ### Java ### 6 | # Compiled class file 7 | *.class 8 | 9 | # Log file 10 | *.log 11 | 12 | # BlueJ files 13 | *.ctxt 14 | 15 | # Mobile Tools for Java (J2ME) 16 | .mtj.tmp/ 17 | 18 | # Package Files # 19 | *.jar 20 | *.war 21 | *.nar 22 | *.ear 23 | *.zip 24 | *.tar.gz 25 | *.rar 26 | 27 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 28 | hs_err_pid* 29 | 30 | ### JetBrains+all ### 31 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 32 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 33 | 34 | # User-specific stuff 35 | .idea/**/workspace.xml 36 | .idea/**/tasks.xml 37 | .idea/**/usage.statistics.xml 38 | .idea/**/dictionaries 39 | .idea/**/shelf 40 | 41 | # Generated files 42 | .idea/**/contentModel.xml 43 | 44 | # Sensitive or high-churn files 45 | .idea/**/dataSources/ 46 | .idea/**/dataSources.ids 47 | .idea/**/dataSources.local.xml 48 | .idea/**/sqlDataSources.xml 49 | .idea/**/dynamic.xml 50 | .idea/**/uiDesigner.xml 51 | .idea/**/dbnavigator.xml 52 | 53 | # Gradle 54 | .idea/**/gradle.xml 55 | .idea/**/libraries 56 | 57 | # Gradle and Maven with auto-import 58 | # When using Gradle or Maven with auto-import, you should exclude module files, 59 | # since they will be recreated, and may cause churn. Uncomment if using 60 | # auto-import. 61 | # .idea/artifacts 62 | # .idea/compiler.xml 63 | # .idea/jarRepositories.xml 64 | # .idea/modules.xml 65 | # .idea/*.iml 66 | # .idea/modules 67 | # *.iml 68 | # *.ipr 69 | 70 | # CMake 71 | cmake-build-*/ 72 | 73 | # Mongo Explorer plugin 74 | .idea/**/mongoSettings.xml 75 | 76 | # File-based project format 77 | *.iws 78 | 79 | # IntelliJ 80 | out/ 81 | 82 | # mpeltonen/sbt-idea plugin 83 | .idea_modules/ 84 | 85 | # JIRA plugin 86 | atlassian-ide-plugin.xml 87 | 88 | # Cursive Clojure plugin 89 | .idea/replstate.xml 90 | 91 | # Crashlytics plugin (for Android Studio and IntelliJ) 92 | com_crashlytics_export_strings.xml 93 | crashlytics.properties 94 | crashlytics-build.properties 95 | fabric.properties 96 | 97 | # Editor-based Rest Client 98 | .idea/httpRequests 99 | 100 | # Android studio 3.1+ serialized cache file 101 | .idea/caches/build_file_checksums.ser 102 | 103 | ### JetBrains+all Patch ### 104 | # Ignores the whole .idea folder and all .iml files 105 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 106 | 107 | .idea/ 108 | 109 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 110 | 111 | *.iml 112 | modules.xml 113 | .idea/misc.xml 114 | *.ipr 115 | 116 | # Sonarlint plugin 117 | .idea/sonarlint 118 | 119 | ### Maven ### 120 | target/ 121 | pom.xml.tag 122 | pom.xml.releaseBackup 123 | pom.xml.versionsBackup 124 | pom.xml.next 125 | release.properties 126 | dependency-reduced-pom.xml 127 | buildNumber.properties 128 | .mvn/timing.properties 129 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 130 | .mvn/wrapper/maven-wrapper.jar 131 | 132 | # End of https://www.toptal.com/developers/gitignore/api/java,maven,jetbrains+all 133 | 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RSocket Broker Gateway MQTT 2 | =========================== 3 | 4 | RSocket MQTT Gateway, 通过MQTT协议访问RSocket的Reactive服务。 5 | 6 | ### Why MQTT with RSocket? 7 | 8 | * Let IoT easy to talk with Reactive Systems 9 | * Connect everything including backend App, Kafka, Data Streams. 10 | 11 | ### MQTT 和 RSocket整合方案 12 | 13 | * MQTT的订阅对应于RSocket request/stream,将MQTT的Topic和一个request/stream的Flux进行关联 14 | * 反向推送: MQTT Gateway提供一个RSocket接口,负责接收发送给MQTT Gateway的消息,然后再将消息转发给MQTT订阅的客户端 15 | 16 | 当然也可以借助于其他的MQ系统,如MQTT Gateway负责和Kafka对接,处理来自Kafka的消息然后再转发给MQTT的订阅者。 17 | 18 | ### MQTT 5 Features 19 | 20 | * Properties in the MQTT Header & Reason Codes: possibility to add custom key-value properties in the MQTT header, MQTT packets include Reason Codes. A Reason Code indicates that a pre-defined protocol error occurred. 21 | * CONNACK Return Codes for unsupported features 22 | * Additional MQTT Packet: new AUTH packet 23 | * New Data Type: UTF-8 String pairs 24 | * Bi-directional DISCONNECT packets 25 | * Using passwords without username 26 | 27 | Netty 4.1.52 has MQTT5 support for netty-codec-mqtt. 28 | 29 | ### References 30 | 31 | * MQTT: http://mqtt.org/ 32 | * Hands-On Internet of Things with MQTT: https://learning.oreilly.com/library/view/hands-on-internet-of/9781789341782 33 | * 初识 MQTT 为什么 MQTT 是最适合物联网的网络协议 https://www.ibm.com/developerworks/cn/iot/iot-mqtt-why-good-for-iot/index.html 34 | * MQTT协议中文: https://mcxiaoke.gitbooks.io/mqtt-cn/content/ 35 | * MQTT 5: hhttps://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html 36 | * Eclipse Mosquitto: open source message broker that implements the MQTT protocol versions 3.1 and 3.1.1 https://mosquitto.org/ 37 | * Eclipse Paho: open-source client implementations of MQTT and MQTT-SN messaging protocols https://www.eclipse.org/paho/ 38 | * HiveMQ: MQTT based messaging platform designed for the fast, efficient and reliable movement of data to and from connected IoT devices https://www.hivemq.com/ 39 | * Reactor Netty: https://github.com/reactor/reactor-netty 40 | * Open-source IoT Platform: https://github.com/actorcloud/ActorCloud 41 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | mosquitto: 4 | image: eclipse-mosquitto:1.6.12 5 | ports: 6 | - "1883:1883" 7 | - "9001:9001" 8 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # sub topic 2 | sub: 3 | mosquitto_sub -v -i client007 -h 127.0.0.1 -p 1883 -t 'test/topic' 4 | 5 | # pub message 6 | pub: 7 | mosquitto_pub -h 127.0.0.1 -p 1883 -t 'test/topic' -m 'helloWorld' 8 | 9 | rsocket_pub: 10 | rsc ws://localhost:9001/rsocket --request --route topic.test/topic -d HelloWorld --debug 11 | 12 | # setup 13 | setup: 14 | brew install mosquitto 15 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.alibaba.rsocket.mqtt 6 | rsocket-broker-gateway-mqtt 7 | 0.0.1-SNAPSHOT 8 | rsocket-broker-gateway-mqtt 9 | RSocket Broker Gateway of MQTT 10 | 11 | 12 | 1.8 13 | 2.5.4 14 | 1.1.1 15 | 4.1.68.Final 16 | 17 | 18 | 19 | scm:git:git@github.com:alibaba-rsocket-broker/rsocket-broker-gateway-mqtt.git 20 | scm:git:git@github.com:alibaba-rsocket-broker/rsocket-broker-gateway-mqtt.git 21 | https://github.com/alibaba-rsocket-broker/rsocket-broker-gateway-mqtt 22 | 23 | 24 | 25 | 26 | linux_china 27 | Jacky Chan 28 | libing.chen@gmail.com 29 | https://twitter.com/linux_china 30 | 31 | Developer 32 | 33 | 34 | 35 | 36 | 37 | 38 | io.projectreactor.addons 39 | reactor-extra 40 | 41 | 42 | 43 | io.rsocket 44 | rsocket-core 45 | 46 | 47 | io.rsocket 48 | rsocket-transport-netty 49 | ${rsocket.version} 50 | 51 | 52 | io.rsocket 53 | rsocket-transport-local 54 | ${rsocket.version} 55 | test 56 | 57 | 58 | io.rsocket 59 | rsocket-load-balancer 60 | ${rsocket.version} 61 | 62 | 63 | io.netty 64 | netty-tcnative-boringssl-static 65 | 2.0.43.Final 66 | 67 | 68 | org.jetbrains 69 | annotations 70 | 22.0.0 71 | 72 | 73 | org.springframework.boot 74 | spring-boot-starter-webflux 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-starter-rsocket 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-starter-actuator 83 | 84 | 85 | io.netty 86 | netty-codec-mqtt 87 | 88 | 89 | org.springframework.boot 90 | spring-boot-starter-test 91 | test 92 | 93 | 94 | org.junit.vintage 95 | junit-vintage-engine 96 | 97 | 98 | junit 99 | junit 100 | 101 | 102 | 103 | 104 | io.projectreactor 105 | reactor-test 106 | test 107 | 108 | 109 | 110 | 111 | 112 | 113 | io.netty 114 | netty-bom 115 | ${netty.version} 116 | import 117 | pom 118 | 119 | 120 | io.rsocket 121 | rsocket-bom 122 | ${rsocket.version} 123 | pom 124 | import 125 | 126 | 127 | org.springframework.boot 128 | spring-boot-dependencies 129 | ${spring-boot.version} 130 | import 131 | pom 132 | 133 | 134 | 135 | 136 | 137 | 138 | org.apache.maven.plugins 139 | maven-compiler-plugin 140 | 3.8.1 141 | 142 | 1.8 143 | 1.8 144 | true 145 | 146 | 147 | 148 | org.springframework.boot 149 | spring-boot-maven-plugin 150 | ${spring-boot.version} 151 | 152 | 153 | com.google.cloud.tools 154 | jib-maven-plugin 155 | 3.1.4 156 | 157 | true 158 | 159 | openjdk:8-jdk-slim 160 | 161 | 162 | rsocket/rsocket-gateway-mqtt 163 | 164 | 165 | 166 | 1883 167 | 8883 168 | 9001 169 | 170 | USE_CURRENT_TIMESTAMP 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/rsocket/mqtt/gateway/MqttAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.rsocket.mqtt.gateway; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.EventLoopGroup; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.SocketChannel; 9 | import io.netty.channel.socket.nio.NioServerSocketChannel; 10 | import io.netty.handler.codec.mqtt.MqttDecoder; 11 | import io.netty.handler.codec.mqtt.MqttEncoder; 12 | import org.springframework.beans.BeansException; 13 | import org.springframework.context.ApplicationContext; 14 | import org.springframework.context.ApplicationContextAware; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | 18 | /** 19 | * MQTT auto configuration 20 | * 21 | * @author leijuan 22 | */ 23 | @Configuration 24 | public class MqttAutoConfiguration implements ApplicationContextAware { 25 | private ApplicationContext applicationContext; 26 | 27 | @Override 28 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 29 | this.applicationContext = applicationContext; 30 | } 31 | 32 | /** 33 | * start MQTT server 34 | * 35 | * @return netty loop group 36 | * @throws Exception exception 37 | */ 38 | @Bean(destroyMethod = "shutdownGracefully") 39 | public EventLoopGroup mqttEventLoopGroup() throws Exception { 40 | EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); 41 | ServerBootstrap serverBootstrap = new ServerBootstrap(); 42 | serverBootstrap.group(eventLoopGroup) 43 | .channel(NioServerSocketChannel.class) 44 | .childHandler(new ChannelInitializer() { 45 | @Override 46 | protected void initChannel(SocketChannel socketChannel) throws Exception { 47 | socketChannel.pipeline().addLast(new MqttDecoder()); 48 | socketChannel.pipeline().addLast(MqttEncoder.INSTANCE); 49 | socketChannel.pipeline().addLast("MqttServerHandler", (ChannelHandler) applicationContext.getBean("mqttHandler")); 50 | } 51 | }); 52 | serverBootstrap.bind("0.0.0.0", 1883).sync(); 53 | return eventLoopGroup; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/rsocket/mqtt/gateway/MqttHandler.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.rsocket.mqtt.gateway; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import io.netty.handler.codec.mqtt.*; 7 | import io.netty.handler.timeout.IdleStateHandler; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.context.annotation.Scope; 12 | import org.springframework.stereotype.Component; 13 | import reactor.core.publisher.Flux; 14 | 15 | import java.util.LinkedList; 16 | import java.util.List; 17 | import java.util.UUID; 18 | import java.util.concurrent.atomic.AtomicInteger; 19 | 20 | /** 21 | * MQTT handler for per channel 22 | * 23 | * @author leijuan 24 | */ 25 | @Component("mqttHandler") 26 | @Scope("prototype") 27 | public class MqttHandler extends ChannelInboundHandlerAdapter { 28 | @Autowired 29 | private SubscriptionManager subscriptionManager; 30 | @Autowired 31 | private PublishService publishService; 32 | 33 | private final AtomicInteger atomicInteger = new AtomicInteger(1); 34 | private final String uuid = UUID.randomUUID().toString(); 35 | private String clientId; 36 | private boolean writable = true; 37 | private static final Logger log = LoggerFactory.getLogger(MqttHandler.class); 38 | 39 | /** 40 | * entrance for MQTT interaction 41 | * 42 | * @param ctx channel handler context 43 | * @param msg MQTT message 44 | * @throws Exception exception 45 | */ 46 | @Override 47 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 48 | MqttMessage message = (MqttMessage) msg; 49 | MqttMessageType mqttMessageType = message.fixedHeader().messageType(); 50 | switch (mqttMessageType) { 51 | case CONNECT: 52 | MqttConnAckMessage ackMessage = onConnect((MqttConnectMessage) message, ctx); 53 | ctx.writeAndFlush(ackMessage); 54 | break; 55 | case CONNACK: 56 | break; 57 | case PUBLISH: 58 | MqttPubAckMessage pubAckMessage = onPublish((MqttPublishMessage) message, ctx); 59 | ctx.writeAndFlush(pubAckMessage); 60 | break; 61 | case PUBACK: 62 | onPubAck((MqttPubAckMessage) message, ctx); 63 | break; 64 | case PUBREC: 65 | break; 66 | case PUBREL: 67 | break; 68 | case PUBCOMP: 69 | break; 70 | case SUBSCRIBE: 71 | MqttSubAckMessage subAckMessage = onSubscribe((MqttSubscribeMessage) message, ctx); 72 | ctx.writeAndFlush(subAckMessage); 73 | break; 74 | case SUBACK: 75 | break; 76 | case UNSUBSCRIBE: 77 | MqttUnsubAckMessage unsubAckMessage = onUnSubscribe((MqttUnsubscribeMessage) message, ctx); 78 | ctx.writeAndFlush(unsubAckMessage); 79 | break; 80 | case UNSUBACK: 81 | break; 82 | case PINGREQ: 83 | MqttMessage pingResp = onPing(); 84 | ctx.writeAndFlush(pingResp); 85 | break; 86 | case PINGRESP: 87 | break; 88 | case DISCONNECT: 89 | onDisconnect(ctx); 90 | break; 91 | default: 92 | log.error("Unknown message type:" + mqttMessageType.value()); 93 | break; 94 | } 95 | } 96 | 97 | private void onDisconnect(ChannelHandlerContext ctx) { 98 | ctx.close(); 99 | } 100 | 101 | private void onPubAck(MqttPubAckMessage message, ChannelHandlerContext ctx) { 102 | System.out.println("pub ack"); 103 | } 104 | 105 | private MqttPubAckMessage onPublish(MqttPublishMessage message, ChannelHandlerContext ctx) { 106 | MqttPublishVariableHeader variableHeader = message.variableHeader(); 107 | String topicName = variableHeader.topicName(); 108 | publishService.publish(clientId, topicName, message.payload()).subscribe(); 109 | MqttFixedHeader ackFixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK, false, MqttQoS.AT_LEAST_ONCE, false, 2); 110 | MqttMessageIdVariableHeader messageIdVariableHeader = MqttMessageIdVariableHeader.from(Math.abs(message.variableHeader().packetId())); 111 | return new MqttPubAckMessage(ackFixedHeader, messageIdVariableHeader); 112 | } 113 | 114 | private MqttMessage onPing() { 115 | MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PINGRESP, false, MqttQoS.AT_MOST_ONCE, false, 0); 116 | return new MqttMessage(mqttFixedHeader); 117 | } 118 | 119 | private MqttUnsubAckMessage onUnSubscribe(MqttUnsubscribeMessage message, ChannelHandlerContext ctx) { 120 | Flux.fromIterable(message.payload().topics()) 121 | .flatMap(topic -> subscriptionManager.cancel(uuid, clientId, topic)) 122 | .subscribe(); 123 | MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBACK, 124 | false, MqttQoS.AT_MOST_ONCE, false, 2); 125 | MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(message.variableHeader().messageId()); 126 | return new MqttUnsubAckMessage(fixedHeader, variableHeader); 127 | } 128 | 129 | private MqttSubAckMessage onSubscribe(MqttSubscribeMessage message, ChannelHandlerContext ctx) { 130 | Channel ch = ctx.channel(); 131 | MqttMessageIdVariableHeader subMsgVariableHeader = message.variableHeader(); 132 | List subscriptions = message.payload().topicSubscriptions(); 133 | List qosList = new LinkedList<>(); 134 | //todo subscribe 135 | for (MqttTopicSubscription topicSubscription : subscriptions) { 136 | qosList.add(topicSubscription.qualityOfService().value()); 137 | Subscription subscription = new Subscription(uuid, clientId, topicSubscription.qualityOfService(), topicSubscription.topicName(), atomicInteger); 138 | subscriptionManager.requestStream(subscription).subscribe(ch::writeAndFlush); 139 | } 140 | //sub ack return 141 | MqttSubAckPayload mqttSubAckPayload = new MqttSubAckPayload(qosList); 142 | MqttFixedHeader mqttFixedHeader = new MqttFixedHeader( 143 | MqttMessageType.SUBACK, 144 | false, 145 | MqttQoS.AT_MOST_ONCE, 146 | false, 147 | subMsgVariableHeader.messageId()); 148 | return new MqttSubAckMessage( 149 | mqttFixedHeader, 150 | subMsgVariableHeader, 151 | mqttSubAckPayload); 152 | } 153 | 154 | private MqttConnAckMessage onConnect(MqttConnectMessage mqttMessage, ChannelHandlerContext channelHandlerContext) { 155 | MqttConnectVariableHeader variableHeader = mqttMessage.variableHeader(); 156 | this.clientId = mqttMessage.payload().clientIdentifier(); 157 | //heart beat 158 | int keepAlive = variableHeader.keepAliveTimeSeconds(); 159 | channelHandlerContext.pipeline().addBefore( 160 | "MqttServerHandler", 161 | "MqttIdleHandler", 162 | new IdleStateHandler(keepAlive, 0, 0)); 163 | MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.FAILURE, false, 0x02); 164 | MqttConnAckVariableHeader connAckVariableHeader = new MqttConnAckVariableHeader(MqttConnectReturnCode.CONNECTION_ACCEPTED, false); 165 | return new MqttConnAckMessage(fixedHeader, connAckVariableHeader); 166 | } 167 | 168 | 169 | @Override 170 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 171 | writable = true; 172 | //channel active 173 | log.info("channelActive"); 174 | } 175 | 176 | @Override 177 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 178 | writable = false; 179 | //todo clean resources after connection closed 180 | log.info("channelUnregistered"); 181 | } 182 | 183 | @Override 184 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 185 | writable = false; 186 | //channel in active, don't send message 187 | log.info("channelInactive"); 188 | } 189 | 190 | @Override 191 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 192 | log.error(cause.getMessage()); 193 | ctx.close(); 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/rsocket/mqtt/gateway/PublishService.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.rsocket.mqtt.gateway; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import reactor.core.publisher.Mono; 5 | 6 | /** 7 | * Publish service 8 | * 9 | * @author leijuan 10 | */ 11 | public interface PublishService { 12 | 13 | Mono publish(String clientId, String topic, ByteBuf payload); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/rsocket/mqtt/gateway/RSocketBrokerGatewayMqttServer.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.rsocket.mqtt.gateway; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * RSocket broker gateway MQTT Server 8 | * 9 | * @author leijuan 10 | */ 11 | @SpringBootApplication 12 | public class RSocketBrokerGatewayMqttServer { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(RSocketBrokerGatewayMqttServer.class, args); 16 | } 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/rsocket/mqtt/gateway/RSocketController.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.rsocket.mqtt.gateway; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.messaging.handler.annotation.DestinationVariable; 5 | import org.springframework.messaging.handler.annotation.MessageMapping; 6 | import org.springframework.stereotype.Controller; 7 | import reactor.core.publisher.Mono; 8 | 9 | /** 10 | * RSocket Controller 11 | * 12 | * @author leijuan 13 | */ 14 | @Controller 15 | public class RSocketController { 16 | @Autowired 17 | private TopicStore topicStore; 18 | 19 | @MessageMapping("topic.{name}") 20 | public Mono send(@DestinationVariable("name") String name, String payload) { 21 | return topicStore.save(name, payload.getBytes()) 22 | .then(Mono.just("Success")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/rsocket/mqtt/gateway/Subscription.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.rsocket.mqtt.gateway; 2 | 3 | import io.netty.handler.codec.mqtt.MqttQoS; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * subscription from client 9 | * 10 | * @author leijuan 11 | */ 12 | public class Subscription { 13 | private String uuid; 14 | private String clientId; 15 | private MqttQoS mqttQoS; 16 | private String topicName; 17 | private AtomicInteger atomicInteger; 18 | 19 | public Subscription() { 20 | } 21 | 22 | public Subscription(String uuid, String clientId, MqttQoS mqttQoS, String topicName, AtomicInteger atomicInteger) { 23 | this.uuid = uuid; 24 | this.clientId = clientId; 25 | this.mqttQoS = mqttQoS; 26 | this.topicName = topicName; 27 | this.atomicInteger = atomicInteger; 28 | } 29 | 30 | public String getUuid() { 31 | return uuid; 32 | } 33 | 34 | public void setUuid(String uuid) { 35 | this.uuid = uuid; 36 | } 37 | 38 | public String getClientId() { 39 | return clientId; 40 | } 41 | 42 | public void setClientId(String clientId) { 43 | this.clientId = clientId; 44 | } 45 | 46 | public MqttQoS getMqttQoS() { 47 | return mqttQoS; 48 | } 49 | 50 | public void setMqttQoS(MqttQoS mqttQoS) { 51 | this.mqttQoS = mqttQoS; 52 | } 53 | 54 | public String getTopicName() { 55 | return topicName; 56 | } 57 | 58 | public void setTopicName(String topicName) { 59 | this.topicName = topicName; 60 | } 61 | 62 | public Integer getNextMessageId() { 63 | return atomicInteger.getAndIncrement(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/rsocket/mqtt/gateway/SubscriptionManager.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.rsocket.mqtt.gateway; 2 | 3 | import io.netty.handler.codec.mqtt.MqttPublishMessage; 4 | import reactor.core.publisher.Flux; 5 | import reactor.core.publisher.Mono; 6 | 7 | /** 8 | * Subscription manager 9 | * 10 | * @author leijuan 11 | */ 12 | public interface SubscriptionManager { 13 | 14 | Flux requestStream(Subscription subscription); 15 | 16 | Mono cancel(String uuid, String clientId, String topicName); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/rsocket/mqtt/gateway/TopicStore.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.rsocket.mqtt.gateway; 2 | 3 | import reactor.core.publisher.Flux; 4 | import reactor.core.publisher.Mono; 5 | 6 | /** 7 | * Topic store 8 | * 9 | * @author leijuan 10 | */ 11 | public interface TopicStore { 12 | 13 | Mono save(String topicName, byte[] bytes); 14 | 15 | Flux receive(String topicName); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/rsocket/mqtt/gateway/impl/PublishServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.rsocket.mqtt.gateway.impl; 2 | 3 | import com.alibaba.rsocket.mqtt.gateway.PublishService; 4 | import com.alibaba.rsocket.mqtt.gateway.TopicStore; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.buffer.ByteBufUtil; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Component; 9 | import reactor.core.publisher.Mono; 10 | 11 | /** 12 | * publish service implementation 13 | * 14 | * @author leijuan 15 | */ 16 | @Component 17 | public class PublishServiceImpl implements PublishService { 18 | @Autowired 19 | private TopicStore topicStore; 20 | 21 | @Override 22 | public Mono publish(String clientId, String topic, ByteBuf payload) { 23 | topicStore.save(topic, ByteBufUtil.getBytes(payload)); 24 | return Mono.empty(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/rsocket/mqtt/gateway/impl/SimpleTopicStore.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.rsocket.mqtt.gateway.impl; 2 | 3 | import com.alibaba.rsocket.mqtt.gateway.TopicStore; 4 | import org.springframework.stereotype.Repository; 5 | import reactor.core.publisher.Flux; 6 | import reactor.core.publisher.Mono; 7 | import reactor.extra.processor.TopicProcessor; 8 | 9 | import java.util.Map; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | 12 | /** 13 | * Simple topic store 14 | * 15 | * @author leijuan 16 | */ 17 | @Repository 18 | public class SimpleTopicStore implements TopicStore { 19 | private Map> topics = new ConcurrentHashMap<>(); 20 | 21 | @Override 22 | public Mono save(String topicName, byte[] bytes) { 23 | if (!topics.containsKey(topicName)) { 24 | initTopic(topicName); 25 | } 26 | topics.get(topicName).onNext(bytes); 27 | return Mono.empty(); 28 | } 29 | 30 | @Override 31 | public Flux receive(String topicName) { 32 | if (!topics.containsKey(topicName)) { 33 | initTopic(topicName); 34 | //todo request/stream remote RSocket 35 | } 36 | return topics.get(topicName); 37 | } 38 | 39 | private void initTopic(String topicName) { 40 | topics.putIfAbsent(topicName, TopicProcessor.create()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/alibaba/rsocket/mqtt/gateway/impl/SubscriptionManagerImpl.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.rsocket.mqtt.gateway.impl; 2 | 3 | import com.alibaba.rsocket.mqtt.gateway.Subscription; 4 | import com.alibaba.rsocket.mqtt.gateway.SubscriptionManager; 5 | import com.alibaba.rsocket.mqtt.gateway.TopicStore; 6 | import io.netty.buffer.Unpooled; 7 | import io.netty.handler.codec.mqtt.*; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | import reactor.core.publisher.Flux; 11 | import reactor.core.publisher.Mono; 12 | 13 | /** 14 | * Subscription manager implementation 15 | * 16 | * @author leijuan 17 | */ 18 | @Component 19 | public class SubscriptionManagerImpl implements SubscriptionManager { 20 | @Autowired 21 | private TopicStore topicStore; 22 | 23 | @Override 24 | public Flux requestStream(Subscription subscription) { 25 | return topicStore.receive(subscription.getTopicName()).map(bytes -> createPublishMessage(subscription, bytes)); 26 | } 27 | 28 | @Override 29 | public Mono cancel(String uuid, String clientId, String topicName) { 30 | return Mono.empty(); 31 | } 32 | 33 | public MqttPublishMessage createPublishMessage(Subscription subscription, byte[] bytes) { 34 | MqttFixedHeader header = new MqttFixedHeader(MqttMessageType.PUBLISH, false, MqttQoS.AT_MOST_ONCE, false, 0); 35 | MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(subscription.getTopicName(), subscription.getNextMessageId()); 36 | return (MqttPublishMessage) MqttMessageFactory.newMessage(header, variableHeader, Unpooled.wrappedBuffer(bytes)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=rsocket-broker-gateway-mqtt 2 | server.port=9001 3 | ### management 4 | management.endpoints.web.exposure.include=* 5 | management.endpoint.health.show-details=always 6 | 7 | ### rsocket 8 | spring.rsocket.server.transport=websocket 9 | spring.rsocket.server.mapping-path=/rsocket 10 | 11 | #logging 12 | logging.level.root=info -------------------------------------------------------------------------------- /src/test/java/com/alibaba/rsocket/mqtt/gateway/SpringBootBaseTestCase.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.rsocket.mqtt.gateway; 2 | 3 | import org.springframework.boot.test.context.SpringBootTest; 4 | 5 | /** 6 | * spring boot base test case 7 | * 8 | * @author leijuan 9 | */ 10 | @SpringBootTest 11 | public abstract class SpringBootBaseTestCase { 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/com/alibaba/rsocket/mqtt/gateway/impl/SimpleTopicStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.alibaba.rsocket.mqtt.gateway.impl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.nio.charset.StandardCharsets; 6 | 7 | /** 8 | * SimpleTopic test 9 | * 10 | * @author leijuan 11 | */ 12 | public class SimpleTopicStoreTest { 13 | private static SimpleTopicStore topicStore = new SimpleTopicStore(); 14 | 15 | @Test 16 | public void testPubSub() throws Exception { 17 | String topicName = "test/topic"; 18 | topicStore.receive(topicName).subscribe(bytes -> { 19 | System.out.println("Received:" + new String(bytes)); 20 | }); 21 | topicStore.save(topicName, "Hello".getBytes(StandardCharsets.UTF_8)).subscribe(); 22 | Thread.sleep(1000); 23 | } 24 | } 25 | --------------------------------------------------------------------------------