I am developing an android application that exchanges data with a jetty server configured in Spring. To obtain a more dynamic android application, i am trying to use WebSocket protocol with Stomp messages.
In order to realize this stuff, i config a web socket message broker in spring :
@Configuration
//@EnableScheduling
@ComponentScan(
basePackages="project.web",
excludeFilters = @ComponentScan.Filter(type= FilterType.ANNOTATION, value = Configuration.class)
)
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/message");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/client");
}
}
and a SimpMessageSendingOperations
in Spring controller to send message from server to client :
@Controller
public class MessageAddController {
private final Log log = LogFactory.getLog(MessageAddController.class);
private SimpMessageSendingOperations messagingTemplate;
private UserManager userManager;
private MessageManager messageManager;
@Autowired
public MessageAddController(SimpMessageSendingOperations messagingTemplate,
UserManager userManager, MessageManager messageManager){
this.messagingTemplate = messagingTemplate;
this.userManager = userManager;
this.messageManager = messageManager;
}
@RequestMapping("/Message/Add")
@ResponseBody
public SimpleMessage addFriendship(
@RequestParam String content,
@RequestParam Long otherUser_id
){
if(log.isInfoEnabled())
log.info("Execute MessageAdd action");
SimpleMessage simpleMessage;
try{
User curentUser = userManager.getCurrentUser();
User otherUser = userManager.findUser(otherUser_id);
Message message = new Message();
message.setContent(content);
message.setUserSender(curentUser);
message.setUserReceiver(otherUser);
messageManager.createMessage(message);
Message newMessage = messageManager.findLastMessageCreated();
messagingTemplate.convertAndSend(
"/message/add", newMessage);//send message through websocket
simpleMessage = new SimpleMessage(null, newMessage);
} catch (Exception e) {
if(log.isErrorEnabled())
log.error("A problem of type : " + e.getClass()
+ " has occured, with message : " + e.getMessage());
simpleMessage = new SimpleMessage(
new SimpleException(e.getClass(), e.getMessage()), null);
}
return simpleMessage;
}
}
When i test this configuration in a web browser with stomp.js, I haven't any problem : messages are perfectly exchanged between web browser and Jetty server. The JavaScript code using for web browser test :
var stompClient = null;
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
document.getElementById('response').innerHTML = '';
}
function connect() {
stompClient = Stomp.client("ws://YOUR_IP/client");
stompClient.connect({}, function(frame) {
setConnected(true);
stompClient.subscribe('/message/add', function(message){
showMessage(JSON.parse(message.body).content);
});
});
}
function disconnect() {
stompClient.disconnect();
setConnected(false);
console.log("Disconnected");
}
function showMessage(message) {
var response = document.getElementById('response');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(message));
response.appendChild(p);
}
Problems occur when i try to use stomp in Android with libraries like gozirra, activemq-stomp or others : most of time, connection with server doesn't work. My app stop to run and, after a few minutes, i have the following message in logcat : java.net.UnknownHostException: Unable to resolve host "ws://192.168.1.39/client": No address associated with hostname
, and i don't understand why. Code using Gozzira library which manages the stomp appeal in my android activity :
private void stomp_test() {
String ip = "ws://192.172.6.39/client";
int port = 8080;
String channel = "/message/add";
Client c;
try {
c = new Client( ip, port, "", "" );
Log.i("Stomp", "Connection established");
c.subscribe( channel, new Listener() {
public void message( Map header, String message ) {
Log.i("Stomp", "Message received!!!");
}
});
} catch (IOException ex) {
Log.e("Stomp", ex.getMessage());
ex.printStackTrace();
} catch (LoginException ex) {
Log.e("Stomp", ex.getMessage());
ex.printStackTrace();
} catch (Exception ex) {
Log.e("Stomp", ex.getMessage());
ex.printStackTrace();
}
}
After some research, i found that most persons who want to use stomp over websocket with Java Client use ActiveMQ server, like in this site. But spring tools are very simple to use and it will be cool if i could keep my server layer as is it now. Someone would know how to use stomp java (Android) in client side with Spring configuration in server side?
My implementation of STOMP protocol for android (or plain java) with RxJava https://github.com/NaikSoftware/StompProtocolAndroid. Tested on STOMP server with SpringBoot. Simple example (with retrolambda):
private StompClient mStompClient;
// ...
mStompClient = Stomp.over(WebSocket.class, "ws://localhost:8080/app/hello/websocket");
mStompClient.connect();
mStompClient.topic("/topic/greetings").subscribe(topicMessage -> {
Log.d(TAG, topicMessage.getPayload());
});
mStompClient.send("/app/hello", "My first STOMP message!");
// ...
mStompClient.disconnect();
Add the following classpath in project :
classpath 'me.tatarka:gradle-retrolambda:3.2.0'
Add the following thing in your app build.gradle :
apply plugin: 'me.tatarka.retrolambda'
android {
.............
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
............................
compile 'org.java-websocket:Java-WebSocket:1.3.0'
compile 'com.github.NaikSoftware:StompProtocolAndroid:1.1.5'
}
All working asynchronously! You can call connect()
after subscribe()
and send()
, messages will be pushed to queue.
Additional features:
For Example :
public class MainActivity extends AppCompatActivity {
private StompClient mStompClient;
public static final String TAG="StompClient";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button view = (Button) findViewById(R.id.button);
view.setOnClickListener(e-> new LongOperation().execute(""));
}
private class LongOperation extends AsyncTask<String, Void, String> {
private StompClient mStompClient;
String TAG="LongOperation";
@Override
protected String doInBackground(String... params) {
mStompClient = Stomp.over(WebSocket.class, "ws://localhost:8080/app/hello/websocket");
mStompClient.connect();
mStompClient.topic("/topic/greetings").subscribe(topicMessage -> {
Log.d(TAG, topicMessage.getPayload());
});
mStompClient.send("/app/hello", "My first STOMP message!").subscribe();
mStompClient.lifecycle().subscribe(lifecycleEvent -> {
switch (lifecycleEvent.getType()) {
case OPENED:
Log.d(TAG, "Stomp connection opened");
break;
case ERROR:
Log.e(TAG, "Error", lifecycleEvent.getException());
break;
case CLOSED:
Log.d(TAG, "Stomp connection closed");
break;
}
});
return "Executed";
}
@Override
protected void onPostExecute(String result) {
}
}
}
Add Internet permission in manifest.xml
<uses-permission android:name="android.permission.INTERNET" />