3

This question is Extension of my previous question on this SO question "How to connect XMPP bosh server using java smack library?"

I am using Java as server side language. I have successfully implement xmpp BOSH connection using smach-jbosh thanks to @Deuteu for helping me to achieve this, so far I have modify jbosh's BOSHClient.java file and added two getter method for extracting RID and SID.

Now I have RID and SID on my app server (I am using Apache Tomcat). I need to pass this credential to Strophe (web client) so that it can attach to connection.

Here I have some doubt.

  1. When to disconnect bosh Connection establish from the app server? before passing sid, rid and jid to strophe or after passing sid, rid and jid to strophe?
    As per my observation during implementation for the same, I have observed that once bosh connection from the app server has been disconnected, session is expired and SID and RID is no longer useful!!!
  2. I have implemented this logic (Establishing bosh connection and Extracting sid and rid) on a Servlet, here once response has been send from Servlet, Thread will get expired and end BOSH connection will get terminated, so I am not able perform `Attach()` on strophe as session is expired.

Can somebody help me with that problem?

Community
  • 1
  • 1
Dev
  • 2,326
  • 24
  • 45

2 Answers2

4

I believe @fpsColton's answer is correct - I'm just added extra info for clarity. As requested on linked thread here is the code changes I made on this - note: I only added the parts where I've labelled "DH"

In BOSHConnection:

 // DH: function to preserve current api
public void login(String username, String password, String resource)
        throws XMPPException {
    login(username, password, resource, false);         

}

// DH: Most of this is existing login function, but added prebind parameter 
//     to allow leaving function after all required pre-bind steps done and before 
//     presence stanza gets sent (sent from attach in XMPP client)
public void login(String username, String password, String resource, boolean preBind)         
        throws XMPPException {
    if (!isConnected()) {
        throw new IllegalStateException("Not connected to server.");
    }
    if (authenticated) {
        throw new IllegalStateException("Already logged in to server.");
    }
    // Do partial version of nameprep on the username.
    username = username.toLowerCase().trim();

    String response;
    if (config.isSASLAuthenticationEnabled()
            && saslAuthentication.hasNonAnonymousAuthentication()) {
        // Authenticate using SASL
        if (password != null) {
            response = saslAuthentication.authenticate(username, password, resource);
        } else {
            response = saslAuthentication.authenticate(username, resource, config.getCallbackHandler());
        }
    } else {
        // Authenticate using Non-SASL
        response = new NonSASLAuthentication(this).authenticate(username, password, resource);
    }

    // Indicate that we're now authenticated.
    authenticated = true;
    anonymous = false;

    // DH: Prebind only requires connect and authenticate
    if (preBind) {
        return;
    }

    // Set the user.
    if (response != null) {
        this.user = response;
        // Update the serviceName with the one returned by the server
        config.setServiceName(StringUtils.parseServer(response));
    } else {
        this.user = username + "@" + getServiceName();
        if (resource != null) {
            this.user += "/" + resource;
        }
    }

    // Create the roster if it is not a reconnection.
    if (this.roster == null) {
        this.roster = new Roster(this);
    }
    if (config.isRosterLoadedAtLogin()) {
        this.roster.reload();
    }

    // Set presence to online.
    if (config.isSendPresence()) {
        sendPacket(new Presence(Presence.Type.available));
    }

    // Stores the autentication for future reconnection
    config.setLoginInfo(username, password, resource);

    // If debugging is enabled, change the the debug window title to include
    // the
    // name we are now logged-in as.l
    if (config.isDebuggerEnabled() && debugger != null) {
        debugger.userHasLogged(user);
    }
}

and

 // DH
@Override
public void disconnect() {
    client.close();
}

then my Client-side (Web Server) wrapper class - for connecting from within JSP is:

Note: This is proving code rather than production - so there's some stuff in here you may not want.

public class SmackBoshConnector {

private String sessionID = null;
private String authID = null;
private Long requestID = 0L;
private String packetID = null;
private boolean connected = false;

public boolean connect(String userName, String password, String host, int port, final String xmppService) {

    boolean success = false;

    try {

        Enumeration<SaslClientFactory> saslFacts = Sasl.getSaslClientFactories();
        if (!saslFacts.hasMoreElements()) {
            System.out.println("Sasl Provider not pre-loaded"); 
            int added = Security.addProvider(new com.sun.security.sasl.Provider()); 
            if (added == -1) {
                System.out.println("Sasl Provider could not be loaded");
                System.exit(added);
            }
            else {
                System.out.println("Sasl Provider added"); 
            }                                                      
        }

        BOSHConfiguration config = new BOSHConfiguration(false, host, port, "/http-bind/", xmppService);
        BOSHConnection connection = new BOSHConnection(config);      

        PacketListener sndListener = new PacketListener() {

            @Override
            public void processPacket(Packet packet) {
                SmackBoshConnector.this.packetID = packet.getPacketID();
                System.out.println("Send PacketId["+packetID+"] to["+packet.toXML()+"]");
            }

        };

        PacketListener rcvListener = new PacketListener() {

            @Override
            public void processPacket(Packet packet) {
                SmackBoshConnector.this.packetID = packet.getPacketID();
                System.out.println("Rcvd PacketId["+packetID+"] to["+packet.toXML()+"]");
            }

        };

        PacketFilter packetFilter = new PacketFilter() {

            @Override
            public boolean accept(Packet packet) {
                return true;
            }
        };

        connection.addPacketSendingListener(sndListener, packetFilter);
        connection.addPacketListener(rcvListener, packetFilter);
        connection.connect();

        // login with pre-bind only
        connection.login(userName, password, "", true);                  

        authID = connection.getConnectionID();

        BOSHClient client = connection.getClient();

        sessionID = client.getSid();
        requestID = client.getRid();

        System.out.println("Connected ["+authID+"] sid["+sessionID+"] rid["+requestID+"]");
        success = true;
        connected = true;

        try {
            Thread.yield();
            Thread.sleep(500);
        }
        catch (InterruptedException e) {
            // Ignore
        }
        finally {
            connection.disconnect();
        }

    } catch (XMPPException ex) {
        Logger.getLogger(SmackBoshConnector.class.getName()).log(Level.SEVERE, null, ex);
    }

    return success;
}

public boolean isConnected() {
    return connected;
}

public String getSessionID() {
    return sessionID;
}

public String getAuthID() {
    return authID;
}

public String getRequestIDAsString() {
    return Long.toString(requestID);
}

public String getNextRequestIDAsString() {
    return Long.toString(requestID+1);
}
public static void main(String[] args)  {        
    SmackBoshConnector bc = new SmackBoshConnector();        
    bc.connect("dazed", "i3ji44mj7k2qt14djct0t5o709", "192.168.2.15", 5280, "my.xmppservice.com");
 }

}

I confess that I'm don't fully remember why I put the Thread.yield and Thread.sleep(1/2 sec) in here - I think - as you can see with added PacketListener - the lower level functions return after sending data and before getting a response back from the server - and if you disconnect before the server has sent it's response then it (also) causes it to clean up the session and things won't work. However it may be that, as @fpsColton says, this dicsonnect() isn't actually required.

Edit: I now remember a bit more about whay I included sleep() and yield(). I noticed that Smack library includes sleep() in several places, including XMPPConnection.shutdown() as per source. Plus in terms of yield() I had problems in my environment (Java in Oracle Database - probably untypical) when it wasn't included - as per Smack Forum Thread.

Good luck.

Dazed
  • 1,069
  • 9
  • 34
  • your code works fine,after i have modified BOSHConnection class as per your code i am able to remove presence,i have gone through your answer on this link http://stackoverflow.com/questions/23264662/java-trying-to-prebind-converse-js-using-bosh-but-not-able-to-get-the-sid-and/23287076#23287076 here your written " My reasoning here is that the XMPP server will actually see two connections (one from SMACK and one from converse) and it's the one that sends the presence which will end up being target for XMPP messages - and we want that to be converse.",can you please elaborate your answer please – Dev Aug 26 '14 at 12:35
  • i am asking above doubt because you are closing bosh connection on server ,so basically server(app server) is no linger listening than as per your answer if app server sends presents than chat messages will be sent to server(app server) and connection is dropped so messages got lost,my doubt is you are attaching to same connection using sid and rid on strophe so still it is two different connection or same connection? other thing is what if you send present after attaching to bosh connection from strophe ,still messages will be send to server (app server) bosh client? – Dev Aug 26 '14 at 12:40
  • Hi, I'm not sure my reasoning is actually right. I may be wrong on closing server-side connection, but I don’t like leaving connections open I can help it – tidying up may happen on timeout, but we may end up with redundant sockets. However, the BOSH protocol allows for multiple connections for same session –http://xmpp.org/extensions/xep-0124.html#technique - pre-bind appears to use –therefore it should be possible to terminate the server-side connection and establish a client-side one for the same session. Of course this may behave differently on different XMPP server implementations. – Dazed Aug 27 '14 at 14:14
  • In terms of message flow, it may be timing - messages will only flow from XMPP server after a presence so if you delay issuing that until converse is up (by doing there) all messages will flow to the new client-side connection. They *may* also flow to the old server-side connection (if left open) – but the thread has ended and we don’t care – hopefully something errors and cleans up. If presence issued server-side then (as we’ve said) flow starts - before client established messages flow into server side – they get lost if we close the connection or not – as our code doesn’t handle them. – Dazed Aug 27 '14 at 14:14
  • 1
    Alternatively, it’s a linkage thing (my initial reasoning) – if XMPP server uses the presence indicator to tie up which connections to send messages to then we want to make sure we do it from the client-side rather than linking it to the server-side connection. I’m actually less convinced, given the multi-socket support in xmpp.org/extensions/xep-0124.html#technique that this is the case. So if we issue the presence on client-side we cover both cases and if we terminate the connection server-side it’s cleaner. – Dazed Aug 27 '14 at 14:15
  • thanks @dazed for nice explanation, in my case[i am dropping bosh connection from server using smack-bosh and trying to attach on strophe with same sid and rid=rid+1] simply client.close() doesn't work ,strophe unable to attach to session!!!do you have any idea why strophe fails to attach()?? – Dev Aug 27 '14 at 17:17
  • Does it work without the client.close()? As I flagged, it may depend on what XMPP server you are using and how they react to the close (I was using eJabberd) and how quickly the client-side connection is formed. – Dazed Aug 28 '14 at 08:32
  • yes attach works without client.close(),but as you said" the BOSH protocol allows for multiple connections for same session –xmpp.org/extensions/xep-0124.html#technique - pre-bind appears to use –"so in such case server bosh client is kept listening and strophe client is also listening to same session rid fluctuation period(period for one http request) is shorter,I am also using ejabberd(2.1.11),but as you said "how quickly the client-side connection is formed" seem better reason to me!! – Dev Aug 28 '14 at 09:40
  • On "same session rid fluctuation period(period for one http request) is shorter" I'd expect quite a few quick request/response cycles after the attach - as converse is sending presence/roster, recieving roster/presence updates - until it gets to steady state and longer time-out based request/response (Long-Polling). Have you monitored converse on it's own without pre-bind? – Dazed Aug 28 '14 at 10:17
2

After you have created a BOSH session with smack and have extracted the SID+RID values, you need to pass them to Strophe's attach() and from here on out you need to let strophe deal with this connection. Once Strophe has attached, you do not want your server to be doing anything to the connection at all.

If your server side code sends any messages at all to the connection manager after strophe has attached, it's likely that it will send a invalid RID which will cause your session to terminate.

Again, once the session has been established and is usable by strophe, do not attempt to continue using it from the server side. After your server side bosh client completes authentication and you've passed the SID+RID to the page, just destroy the server side connection object, don't attempt to disconnect or anything as this will end your session.

The thing you need to remember is, unlike traditional XMPP connections over TCP, BOSH clients do NOT maintain a persistent connection to the server (this is why we use BOSH in web applications). So there is nothing to disconnect. The persistent connection is actually between the XMPP server and the BOSH connection manager, it's not something you need to deal with. So when you call disconnect from your server side BOSH client, you're telling the connection manager to end the session and close it's connection to the XMPP server, which completely defeats the purpose of creating the session in the first place.

fpsColton
  • 873
  • 5
  • 11
  • thanks for your answer,in your answer you have mention "just destroy the server side connection object, don't attempt to disconnect or anything as this will end your session." does it means that after passing RID and SID to strophe from app server,and strophe succeed to attach to existing connection with bosh client, now if i disconnect connection from app server my current connection with strophe also gets disconnected automatically ?? – Dev Aug 23 '14 at 04:02
  • That is correct, if you call `disconnect()` from your server side BOSH Client, you will be unable to continue using that session with Strophe. You need to understand that calling `disconnect()` from any BOSH client will not 'close a socket' or anything, it will actually send a new HTTP request to your connection manager asking it to end your session. Communication to the BOSH connection manager works like any other HTTP protocol, there's no persistent connection, instead it's a series of seperate requests and responses. – fpsColton Aug 23 '14 at 08:13
  • thanks for the clarity,i cant use disconnect method from the BOSHConnection class ,what about close method of BOSHClient class form jbosh as per the documentation close method "Forcibly close this client session instance. Calling this method simply shuts down the local session without sending a termination message, releasing all resources associated with the session." would this method be helpful for me? – Dev Aug 23 '14 at 09:12
  • 1
    Hi, I believe @fpsColton is correct on using disconnect(Presence), but looking back at my changed version of BOSHConnect I believe I also hit this problem and added another disconnect() function which only called "client.close();". This cleans up all the client side stuff without sending anything to the server. – Dazed Aug 23 '14 at 17:50
  • An update on last (too late to edit) - the super class Connection has a disconnect() funtion which sends a Presence stanza. What I did was add an override function as BOSHConnection.disconnect() which just does the client.close(). – Dazed Aug 23 '14 at 18:03
  • 1
    Yes the close() function is exactly what you need to use, it should just release any local resources bound to the connection object without sending any messages to the connection manger. The reason it's so important that your server code does not send any messages to the xmpp connection manager after strophe has a attatched is because no matter what it sends, the rid associated with the message will be out of order and once the connection manager is sent an unexpected rid it usually ends the session immediately as this is a security mechanism of bosh. – fpsColton Aug 23 '14 at 18:32
  • @ fpsColton thanks your answer , i have tried close() function in my smack code but when i tried to attach on strophe ,bosh manages was not accepting the attach!!!can you please tell me what would be the reasons??again i am using same rid and sid which is passed by app server ,i am also increasing rid to rid+1. – Dev Aug 26 '14 at 12:45
  • @user3523641 Best way to see what's happening is by running a network inspector such as Fiddler2 so you can clearly see what the stanza's look like. Can strophe attach if you don't call `close()`? – fpsColton Aug 26 '14 at 16:21
  • Yes if I don't use close, then bosh connection is on (listening) from server and request polling period is of one min by default means rid doest change, in such case if I attach to same session before. Completition of one min and rid=rid +1,strophe will successfully get attach, but one thing I observed as two bosh client are connected with same session rid will keep changing rapidly..means each http request (polling )is of 2-3 mili sec.!!! – Dev Aug 26 '14 at 19:27
  • @user3523641 Honestly I've never used the smack library so I'm not sure if `BOSHClient.close()` is the correct function to stop all traffic. Is it possible that after the 1 minute request interval occurs, the server side BOSHClient re-sends another request? Once the server side authorization has completed and you've sent your SID+RID to strophe, it's crucial that the BOSHClient on your server stops all communication with the BOSH connection manager. Only one BOSHClient should communicate with the connection manager at any given time. Is there a `pause()` method available? – fpsColton Aug 26 '14 at 20:22
  • as per my observation[i have enable smack debugger which helped me to monitor traffic] after calling client.close(),all further request response [from server side bosh connection] is stopped,yes there is a pause() method in for BOSHClient.Right now i am trying that only.means pausing bosh connection on app server attach on strophe .once strophe successfully attaches to session drop app server side bosh connection!!! – Dev Aug 27 '14 at 17:24
  • sorry for late reply i was busy with checking test cases,finally i succeed on attaching strophe only after pausing bosh connection on server(app server),i paced conn.pause() and after that conn.close(), and on strophe conn.attach(jid,sid,rid),and strophe is able to attach to same bosh session,thanks @fpsColton for Clarifying certain points and solving my doubts. – Dev Sep 03 '14 at 06:40