在服务器端使用Spring框架在android中设置Stomp客户端 [英] Set up a Stomp client in android with Spring framework in server side

查看:178
本文介绍了在服务器端使用Spring框架在android中设置Stomp客户端的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个Android应用程序,该应用程序与Spring中配置的码头服务器交换数据.为了获得更动态的android应用程序,我正在尝试将WebSocket协议与Stomp消息一起使用.

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.

为了实现这些功能,我在春季配置了一个Web套接字消息代理:

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");
    }
}

和Spring控制器中的SimpMessageSendingOperations,用于将消息从服​​务器发送到客户端:

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;
    }
}

当我使用stomp.js在Web浏览器中测试此配置时,我没有任何问题:在Web浏览器和Jetty服务器之间可以完美地交换消息.用于Web浏览器测试的JavaScript代码:

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);
    }

当我尝试在Android中使用gozirra,activemq-stomp之类的库或其他库时,会出现问题:大多数情况下,与服务器的连接不起作用.我的应用程序停止运行,几分钟后,我在logcat中收到以下消息:java.net.UnknownHostException: Unable to resolve host "ws://192.168.1.39/client": No address associated with hostname,我不明白为什么.使用Gozzira库的代码来管理我的android活动中的踩踏上诉:

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();
    }

}

经过研究,我发现大多数想通过Java Client在websocket上使用stomp的人都使用ActiveMQ服务器,例如

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?

推荐答案

我实现了在Android和Spring服务器的Web套接字上使用stomp.

I achieve to use stomp over web socket with Android and spring server.

要做到这一点,我使用了一个Web套接字库:werbench(跟随链接下载)它).要进行安装,我使用了maven命令mvn install,然后将jar恢复到本地存储库中.然后,我需要在基本的Web套接字上添加一个stomp层,但是我在Java中找不到任何可以在Web套接字上管理stomp的stomp库(我不得不放弃gozzira).因此,我创建了自己的一个(使用stomp.js之类的模型).不要犹豫,问我是否要看看它,但我很快意识到了这一点,因此它无法像stomp.js一样多地进行管理.然后,我需要通过我的spring服务器实现身份验证.为此,我遵循此网站.当我取回JSESSIONID cookie时,只需要在我的踩踏库"中的werbench Web套接字的实例化中使用此cookie声明一个标头.

To do such a thing, i used a web socket library : werbench (follow this link to download it). To install, I used the maven command mvn install and i got back the jar in my local repository. Then, I need to add a stomp layer on the basic web socket one, but i couldn't find any stomp library in java which could manage stomp over web socket (I had to give up gozzira). So I create my own one (with stomp.js like model). Don't hesitate to ask me if you want take a look at it, but i realized it very quickly so it can not manage as much as stomp.js. Then, i need to realize an authentication with my spring server. To achieve it, i followed the indication of this site. when i get back the JSESSIONID cookie, I had just need to declare an header with this cookie in the instantiation of a werbench web socket in my stomp "library".

这是该库中的主要类,该类用于管理通过Web套接字连接的脚踩:

EDIT : this is the main class in this library, the one which manage the stomp over web socket connection :

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import android.util.Log;
import de.roderick.weberknecht.WebSocket;
import de.roderick.weberknecht.WebSocketEventHandler;
import de.roderick.weberknecht.WebSocketMessage;

public class Stomp {

    private static final String TAG = Stomp.class.getSimpleName();

    public static final int CONNECTED = 1;//Connection completely established
    public static final int NOT_AGAIN_CONNECTED = 2;//Connection process is ongoing
    public static final int DECONNECTED_FROM_OTHER = 3;//Error, no more internet connection, etc.
    public static final int DECONNECTED_FROM_APP = 4;//application explicitely ask for shut down the connection 

    private static final String PREFIX_ID_SUBSCIPTION = "sub-";
    private static final String ACCEPT_VERSION_NAME = "accept-version";
    private static final String ACCEPT_VERSION = "1.1,1.0";
    private static final String COMMAND_CONNECT = "CONNECT";
    private static final String COMMAND_CONNECTED = "CONNECTED";
    private static final String COMMAND_MESSAGE = "MESSAGE";
    private static final String COMMAND_RECEIPT = "RECEIPT";
    private static final String COMMAND_ERROR = "ERROR";
    private static final String COMMAND_DISCONNECT = "DISCONNECT";
    private static final String COMMAND_SEND = "SEND";
    private static final String COMMAND_SUBSCRIBE = "SUBSCRIBE";
    private static final String COMMAND_UNSUBSCRIBE = "UNSUBSCRIBE";
    private static final String SUBSCRIPTION_ID = "id";
    private static final String SUBSCRIPTION_DESTINATION = "destination";
    private static final String SUBSCRIPTION_SUBSCRIPTION = "subscription";


    private static final Set<String> VERSIONS = new HashSet<String>();
    static {
        VERSIONS.add("V1.0");
        VERSIONS.add("V1.1");
        VERSIONS.add("V1.2");
    }

    private WebSocket websocket;

    private int counter;

    private int connection;

    private Map<String, String> headers;

    private int maxWebSocketFrameSize;

    private Map<String, Subscription> subscriptions;

    private ListenerWSNetwork networkListener;

    /**
     * Constructor of a stomp object. Only url used to set up a connection with a server can be instantiate
     * 
     * @param url
     *      the url of the server to connect with
     */
    public Stomp(String url, Map<String,String> headersSetup, ListenerWSNetwork stompStates){       
        try {
            this.websocket = new WebSocket(new URI(url), null, headersSetup);
            this.counter = 0;

            this.headers = new HashMap<String, String>();
            this.maxWebSocketFrameSize = 16 * 1024;
            this.connection = NOT_AGAIN_CONNECTED;
            this.networkListener = stompStates;
            this.networkListener.onState(NOT_AGAIN_CONNECTED);
            this.subscriptions = new HashMap<String, Subscription>();

            this.websocket.setEventHandler(new WebSocketEventHandler() {        
                @Override
                public void onOpen(){
                    if(Stomp.this.headers != null){                                         
                        Stomp.this.headers.put(ACCEPT_VERSION_NAME, ACCEPT_VERSION);

                        transmit(COMMAND_CONNECT, Stomp.this.headers, null);

                        Log.d(TAG, "...Web Socket Openned");
                    }
                }

                @Override
                public void onMessage(WebSocketMessage message) {
                    Log.d(TAG, "<<< " + message.getText());
                    Frame frame = Frame.fromString(message.getText());
                    boolean isMessageConnected = false;

                    if(frame.getCommand().equals(COMMAND_CONNECTED)){
                        Stomp.this.connection = CONNECTED;
                        Stomp.this.networkListener.onState(CONNECTED);

                        Log.d(TAG, "connected to server : " + frame.getHeaders().get("server"));
                        isMessageConnected = true;

                    } else if(frame.getCommand().equals(COMMAND_MESSAGE)){
                        String subscription = frame.getHeaders().get(SUBSCRIPTION_SUBSCRIPTION);
                        ListenerSubscription onReceive = Stomp.this.subscriptions.get(subscription).getCallback();

                        if(onReceive != null){
                            onReceive.onMessage(frame.getHeaders(), frame.getBody());
                        } else{
                            Log.e(TAG, "Error : Subscription with id = " + subscription + " had not been subscribed");
                            //ACTION TO DETERMINE TO MANAGE SUBCRIPTION ERROR
                        }

                    } else if(frame.getCommand().equals(COMMAND_RECEIPT)){
                        //I DON'T KNOW WHAT A RECEIPT STOMP MESSAGE IS

                    } else if(frame.getCommand().equals(COMMAND_ERROR)){
                        Log.e(TAG, "Error : Headers = " + frame.getHeaders() + ", Body = " + frame.getBody());
                        //ACTION TO DETERMINE TO MANAGE ERROR MESSAGE

                    } else {

                    }

                    if(isMessageConnected)
                        Stomp.this.subscribe();
                }

                @Override
                public void onClose(){
                    if(connection == DECONNECTED_FROM_APP){
                        Log.d(TAG, "Web Socket disconnected");
                        disconnectFromApp();
                    } else{
                        Log.w(TAG, "Problem : Web Socket disconnected whereas Stomp disconnect method has never "
                                + "been called.");
                        disconnectFromServer();
                    }
                }

                @Override
                public void onPing() {

                }

                @Override
                public void onPong() {

                }

                @Override
                public void onError(IOException e) {
                    Log.e(TAG, "Error : " + e.getMessage());                
                }
            });
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    /**
     * Send a message to server thanks to websocket
     * 
     * @param command
     *      one of a frame property, see {@link Frame} for more details
     * @param headers
     *      one of a frame property, see {@link Frame} for more details
     * @param body
     *      one of a frame property, see {@link Frame} for more details
     */
    private void transmit(String command, Map<String, String> headers, String body){
        String out = Frame.marshall(command, headers, body);
        Log.d(TAG, ">>> " + out);
        while (true) {
            if (out.length() > this.maxWebSocketFrameSize) {
                this.websocket.send(out.substring(0, this.maxWebSocketFrameSize));
                out = out.substring(this.maxWebSocketFrameSize);
            } else {
                this.websocket.send(out);
                break;
            }
        }
    }

    /**
     * Set up a web socket connection with a server
     */
    public void connect(){
        if(this.connection != CONNECTED){
            Log.d(TAG, "Opening Web Socket...");
            try{
                this.websocket.connect();
            } catch (Exception e){
                Log.w(TAG, "Impossible to establish a connection : " + e.getClass() + ":" + e.getMessage());
            }
        }
    }

    /**
     * disconnection come from the server, without any intervention of client side. Operations order is very important
     */
    private void disconnectFromServer(){
        if(this.connection == CONNECTED){
            this.connection = DECONNECTED_FROM_OTHER;
            this.websocket.close();
            this.networkListener.onState(this.connection);
        }
    }

    /**
     * disconnection come from the app, because the public method disconnect was called
     */
    private void disconnectFromApp(){
        if(this.connection == DECONNECTED_FROM_APP){
            this.websocket.close();
            this.networkListener.onState(this.connection);
        }
    }

    /**
     * Close the web socket connection with the server. Operations order is very important
     */
    public void disconnect(){
        if(this.connection == CONNECTED){
            this.connection = DECONNECTED_FROM_APP;
            transmit(COMMAND_DISCONNECT, null, null);
        }
    }

    /**
     * Send a simple message to the server thanks to the body parameter
     * 
     * 
     * @param destination
     *      The destination through a Stomp message will be send to the server
     * @param headers
     *      headers of the message
     * @param body
     *      body of a message
     */
    public void send(String destination, Map<String,String> headers, String body){
        if(this.connection == CONNECTED){
            if(headers == null)
                headers = new HashMap<String, String>();

            if(body == null)
                body = "";

            headers.put(SUBSCRIPTION_DESTINATION, destination);

            transmit(COMMAND_SEND, headers, body);
        }
    }

    /**
     * Allow a client to send a subscription message to the server independently of the initialization of the web socket.
     * If connection have not been already done, just save the subscription
     * 
     * @param subscription
     *      a subscription object
     */
    public void subscribe(Subscription subscription){
        subscription.setId(PREFIX_ID_SUBSCIPTION + this.counter++);
        this.subscriptions.put(subscription.getId(), subscription);

        if(this.connection == CONNECTED){   
            Map<String, String> headers = new HashMap<String, String>();            
            headers.put(SUBSCRIPTION_ID, subscription.getId());
            headers.put(SUBSCRIPTION_DESTINATION, subscription.getDestination());

            subscribe(headers);
        }
    }

    /**
     * Subscribe to a Stomp channel, through messages will be send and received. A message send from a determine channel
     * can not be receive in an another.
     *
     */
    private void subscribe(){
        if(this.connection == CONNECTED){
            for(Subscription subscription : this.subscriptions.values()){
                Map<String, String> headers = new HashMap<String, String>();            
                headers.put(SUBSCRIPTION_ID, subscription.getId());
                headers.put(SUBSCRIPTION_DESTINATION, subscription.getDestination());

                subscribe(headers);
            }
        }
    }

    /**
     * Send the subscribe to the server with an header
     * @param headers
     *      header of a subscribe STOMP message
     */
    private void subscribe(Map<String, String> headers){
        transmit(COMMAND_SUBSCRIBE, headers, null);
    }

    /**
     * Destroy a subscription with its id
     * 
     * @param id
     *      the id of the subscription. This id is automatically setting up in the subscribe method
     */
    public void unsubscribe(String id){
        if(this.connection == CONNECTED){
            Map<String, String> headers = new HashMap<String, String>();
            headers.put(SUBSCRIPTION_ID, id);

            this.subscriptions.remove(id);
            this.transmit(COMMAND_UNSUBSCRIBE, headers, null);
        }
    }
}

这是踩踏消息的框架:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Frame {
//  private final static String CONTENT_LENGTH = "content-length";

    private String command;
    private Map<String, String> headers;
    private String body;

    /**
     * Constructor of a Frame object. All parameters of a frame can be instantiate
     * 
     * @param command
     * @param headers
     * @param body
     */
    public Frame(String command, Map<String, String> headers, String body){
        this.command = command;     
        this.headers = headers != null ? headers : new HashMap<String, String>();
        this.body = body != null ? body : "";
    }

    public String getCommand(){
        return command;
    }

    public Map<String, String> getHeaders(){
        return headers;
    }

    public String getBody(){
        return body;
    }

    /**
     * Transform a frame object into a String. This method is copied on the objective C one, in the MMPReactiveStompClient
     * library
     * @return a frame object convert in a String
     */
    private String toStringg(){
        String strLines = this.command;
        strLines += Byte.LF;
        for(String key : this.headers.keySet()){
            strLines += key + ":" + this.headers.get(key);
            strLines += Byte.LF;
        }
        strLines += Byte.LF;
        strLines += this.body;
        strLines += Byte.NULL;

        return strLines;
    }

    /**
     * Create a frame from a received message. This method is copied on the objective C one, in the MMPReactiveStompClient
     * library
     * 
     * @param data
     *  a part of the message received from network, which represented a frame
     * @return
     *  An object frame
     */
    public static Frame fromString(String data){
        List<String> contents = new ArrayList<String>(Arrays.asList(data.split(Byte.LF)));

        while(contents.size() > 0 && contents.get(0).equals("")){
            contents.remove(0);
        }

        String command = contents.get(0);
        Map<String, String> headers = new HashMap<String, String>();
        String body = "";

        contents.remove(0);
        boolean hasHeaders = false;
        for(String line : contents){
            if(hasHeaders){
                for(int i=0; i < line.length(); i++){
                    Character c = line.charAt(i);
                    if(!c.equals('\0'))
                        body += c;
                }
            } else{
                if(line.equals("")){
                    hasHeaders = true;
                } else {
                    String[] header = line.split(":");
                    headers.put(header[0], header[1]);
                }
            }
        }
        return new Frame(command, headers, body);   
    }

//    No need this method, a single frame will be always be send because body of the message will never be excessive
//    /**
//     * Transform a message received from server in a Set of objects, named frame, manageable by java
//     * 
//     * @param datas
//     *        message received from network
//     * @return
//     *        a Set of Frame
//     */
//    public static Set<Frame> unmarshall(String datas){
//      String data;
//      String[] ref = datas.split(Byte.NULL + Byte.LF + "*");//NEED TO VERIFY THIS PARAMETER
//      Set<Frame> results = new HashSet<Frame>();
//      
//      for (int i = 0, len = ref.length; i < len; i++) {
//            data = ref[i];
//            
//            if ((data != null ? data.length() : 0) > 0){
//              results.add(unmarshallSingle(data));//"unmarshallSingle" is the old name method for "fromString"
//            }
//        }         
//      return results;
//    }

    /**
     * Create a frame with based fame component and convert them into a string
     * 
     * @param command
     * @param headers
     * @param body
     * @return  a frame object convert in a String, thanks to <code>toStringg()</code> method
     */
    public static String marshall(String command, Map<String, String> headers, String body){
        Frame frame = new Frame(command, headers, body);
        return frame.toStringg();
    }

    private class Byte {
        public static final String LF = "\n";
        public static final String NULL = "\0";
    }
}

这个对象是用于通过stomp协议建立订阅的对象:

This one is an object used to establish a subscription via the stomp protocol :

public class Subscription {

    private String id;

    private String destination;

    private ListenerSubscription callback;

    public Subscription(String destination, ListenerSubscription callback){
        this.destination = destination;
        this.callback = callback;
    }

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }

    public String getDestination() {
        return destination;
    }

    public ListenerSubscription getCallback() {
        return callback;
    }
}

至少,有两个接口用作运行" java类,用于侦听Web套接字网络和给定的订阅渠道

At least, there are two interfaces used as the "Run" java class, to listen web socket network and a given subscription canal

public interface ListenerWSNetwork {
    public void onState(int state);
}

import java.util.Map;
public interface ListenerSubscription {
    public void onMessage(Map<String, String> headers, String body);
}

有关更多信息,请随时问我.

For more information, don't hesitate to ask me.

这篇关于在服务器端使用Spring框架在android中设置Stomp客户端的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆