├── .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 |
--------------------------------------------------------------------------------