Xmpp websocket connection not working
I am trying to establish Xmpp websocket connection to my IoT gateway (previously working as Xmpp Bosh connection, which failed after a FW upgrade of my gateway). Trailing the websocket connection as websocket now is introduced during login (as identified during browser login / web developer tools).
So, I believe I have imported the correct packages required for websocket connection, and I believe I have a working setup for the Xmpp session:
//
m_WebSocketConfiguration = WebSocketConnectionConfiguration.builder()...
m_XmppConfiguration = XmppSessionConfiguration.builder()...
m_XmppClient = XmppClient.create("domain", m_XmppConfiguration, m_WebSocketConfiguration);
//
// Connect
try {
m_XmppClient.connect();
}
However, as part of WebSocketConnectionConfiguration --> ClientEndpointConfig / XmppWebSocketDecoder (all pointing to javax.websocket) I end up with the following error:
[ERROR] [rnal.handler.FreeAtHomeBridgeHandler] - javax.websocket.DecodeException: unexpected element (uri:"http://etherx.jabber.org/streams", local:"stream"). Expected elements are...
Comments (21)
-
-
repo owner It seems that your server doesn't speak the correct WebSocket protocol as defined in https://tools.ietf.org/html/rfc7395
Opening the stream with <stream:stream> is not allowed.
See here for the correct protocol flow: https://tools.ietf.org/html/rfc7395#section-3.4
(Client sends <open/> and server response with <open/>)
-
And I just realized (have no clue if it makes any difference):
The web browser opening stream uses " ", while there is a mix between ‘ ‘ and " " in the subsequent stream.
-
And also, I am using Xmpp.tryconnect(), and I see several examples using <stream:stream> (ex issue
#130).The IoT server is not open source, but appears as Prosody server through web browser.
It all worked under the previous FW version of the IoT gateway, then using Xmpp BoshConfiguration / XmppClient.connect(). But then the connection also appeared as type http-bind when logging in via browser.
-
repo owner BOSH is a different protocol, your server seems to have it implemented correctly then.
"" or '' don't matter, XML allows both.
<stream:stream> is simply not allowed in WebSocket connections. It's only used in plain ("normal") TCP connection on port 5222. See https://tools.ietf.org/html/rfc7395#section-3.3
The correct initial header is
<open xmlns="urn:ietf:params:xml:ns:xmpp-framing" to="example.com" version="1.0" />
-
Well, managed to read some data from browser .har file from websocket login via web browser: It appears that the gateway server is following a different protocol flow (ietf draft, also from 2014): https://tools.ietf.org/html/draft-ietf-xmpp-websocket-00#section-3.1
And here websocket connection is established with a stream as listed above:
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" to="example.com" version="1.0">
Any tips/ideas for how to customize the existing Rocks XMPP library to fit the server´s response?
-
repo owner Ah interesting. I fear, the library is not customizable enough, to easily use another protocol for XMPP over WebSockets.
You could of course write your own
Draft00WebSocketConnectionConfiguration
,Draft00XmppWebSocketDecoder
,Draft00XmppWebSocketEncoder
andDraft00WebSocketConnection
. It's mostly copy and paste from the existing classes.But honestly, I'd rather put my energy into fixing the server so that it conforms to the final RFC 7395 spec.
EDIT: Already starting with draft 01 the protocol already used <open/>.
-
Appreciate your feedback. Changing the server would be challenging as the server is a ABB brand IoT smarthome gateway (Free@Home).
Strange thing though, fiddling around I managed to connect/open some communication to the IoT gateway (appearing online). So now I have
WebSocketConnectionConfiguration
,XmppSessionConfiguration
andXmppClient = ("domain", XmppSessionConfiguration, WebSocketConnectionConfiguration)
.By running
WebSocketConnectionConfiguration.createConnection(XmppClient)
I got recognition of the gateway as online. However, still not able to read/send actual "data", and I get error as shown below. TryingXmppClient.login()
for resource binding, but haven´t succeeded yet.java.lang.IllegalStateException: Cannot send stanzas before resource binding has completed. at rocks.xmpp.core.session.XmppSession.sendInternal(XmppSession.java:885) ~[?:?] at rocks.xmpp.core.session.XmppSession.trackAndSend(XmppSession.java:1000) ~[?:?] at rocks.xmpp.core.session.XmppSession.sendIQ(XmppSession.java:973) ~[?:?] at rocks.xmpp.core.session.XmppSession.sendAndAwait(XmppSession.java:822) ~[?:?] at rocks.xmpp.core.session.XmppSession.query(XmppSession.java:748) ~[?:?] at rocks.xmpp.core.session.XmppSession.query(XmppSession.java:733) ~[?:?] at rocks.xmpp.extensions.rpc.RpcManager.call(RpcManager.java:113) ~[?:?] at org.openhab.binding.freeathome.internal.handler.FreeAtHomeBridgeHandler.setDataPoint(FreeAtHomeBridgeHandler.java:177) ~[?:?]
-
repo owner WebSocketConnectionConfiguration.createConnection(XmppClient)
is not intended to be used directly, but only by XmppClient#connect()Calling XmppClient#connect() does connect to the WebSocket endpoint on the WebSocket layer (that's why you probably see it online) and after that does some XMPP handshake, e.g. negotiating <stream:features/>.
If you call login() directly, the stream headers are not exchanged and it probably fails therefore.
-
Again, appreciate your effort to help a guy in need.
Have created my own "class/method caller" flow diagram based on my setup to see where I can make potential changes (at least for the initial stream, assuming the rest of the stream could be fetched by the listeners/negiotiator). I now have the
WebSocketConnectionConfiguration, XmppSessionConfiguration and XmppClient = ("domain", XmppSessionConfiguration, WebSocketConnectionConfiguration)
But I am now aiming for
XmppClient.connect(Jid from)
and subsequentlyXmppClient.login("user", "pwd")
However, based on my flow diagram, I do not see any obvious places where to replace <open> to <stream> (ref ietf documentation). You mentioned that it would be needed to adjust both WebSocketConnectionConfiguration, WebSocketDecoder, WebSocketEncoder and WebSocketConnection. However, WebSocketConnection is the only class making reference to"open".
Hope you will bare with me, as I am a 1 month old Java / Xmpp experimenter.
-
repo owner WebSocketConnection
sends the Open element, yes. You need to replace it with StreamHeader or simple pass the SessionOpen from argument to the send method.XmppWebSocketEncoder
eventually takes this element and encodes it to XML. You could check, if it's aStreamHeader
and if yes, pass theXMLStreamWriter
to thewriteTo
method. The goal is to return the StreamHeader as XML string from the encode method.XmppWebSocketDecoder
does the opposite. Unfortunately decoding is a little bit harder, especially if there's only a partial XML element (stream header). I suggest you useXMLEventReader
orXMLStreamReader
. Take a look atXmppStreamReader
, which reads the header from an InputStream.After decoding, the elements are passed to the message handler in WebSocketConnection again (private
onRead
method, which you need to adjust). -
Sorry for bothering you with a problem which really isn't a XMPP library problem, rather an issue for my so-called websocket server.
I have followed your tips for the initial output stream, whereas I now have
XmppSession.tryconnect
-->New WebSocketClientConnection
-->WebSocketConnection.open(SessionOpen (= StreamHeader))
-->Return send (SessionOpen)
Furthermore, I have adjusted the code for
XmppWebSocketEncoder
to check ifStreamElement
is instance ofStreamHeader
:public final String encode(final StreamElement object) throws EncodeException { try (Writer writer = new StringWriter()) { XMLStreamWriter xmlStreamWriter = null; try { if (object instanceof StreamHeader) { object.writeTo(xmlStreamWriter); marshaller.get().marshal(object, xmlStreamWriter); String xml = xmlStreamWriter.toString(); if (interceptor != null) { interceptor.accept(xml, object); } return xml; } else { xmlStreamWriter = XmppUtils.createXmppStreamWriter(xmlOutputFactory.createXMLStreamWriter(writer), object instanceof StreamFeatures || object instanceof StreamError); marshaller.get().marshal(object, xmlStreamWriter); xmlStreamWriter.flush(); String xml = writer.toString(); if (interceptor != null) { interceptor.accept(xml, object); } return xml; } } finally { if (xmlStreamWriter != null) { xmlStreamWriter.close(); } } } catch (Exception e) { throw new EncodeException(object, e.getMessage(), e); } }
However, with this adjustment, I still end up with the same error message:
rocks.xmpp.core.XmppException: javax.websocket.DecodeException: unexpected element (uri:"http://etherx.jabber.org/streams", local:"stream"). Expected elements are <{urn:xmpp:sm:3}a>,<{urn:ietf:params:xml:ns:xmpp-sasl}abort>.....
I have no clue if the error message relates to the initial output stream sent to the server, or if the server replies with an actual opening stream which just is decoded as error by the code.
I see from
StreamHeader.writeTo
that the method sets up startelement with the"http://etherx.jabber.org/streams"
part as the first input. Not sure if sequence of elements would matter?Based on webbrowser login, the following opening stream is actually happening:
<stream:stream to="domain" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0"> <?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' version='1.0' from='domain id='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' xml:lang='en' xmlns='jabber:client'>
-
repo owner You need to adjust the Decoder, too. The exception occurs, because the current decoder expects full XML elements, (e.g. with start and end tag), which then are put into the unmarshaller and are unmarshalled to an Java object. However, <stream:stream/> is only an opening tag and cannot be put into the unmarshaller, but needs manual parsing instead.
The order of attributes doesn't matter.
-
Just to ensure I understand it correctly,
WebSocketEncoder
handles client-server stream andWebSocketDecoder
handles server-client stream? Meaning, based on the exception I get, my setup manages the client-server starting stream but struggle to handle the starting stream from the server?I have had a look at the
XmppStreamReader
andWebSocketDecoder
, but without understanding where the exception occur.XmppStreamReader
seem to be able to handle <stream:stream/> throughisStartElement
(which is the actual opening stream from the server), and I do not see whyWebSocketDecoder
will throw an exception unless the unmarshaller itself creates such an exception? Not sure how to overcome this, as I assume any stream need to go via unmarshaller? -
repo owner Yes, you understood correctly. The exception occurs in the
XmppWebSocketDecoder#decode()
method. The unmarshaller doesn't except an half-open element <stream:stream/> (without end tag), throwsJAXBException
, which is converted toDecodeException
. Unmarshaller can only unmarshal complete XML (i.e. with start and end tag). -
Maybe we should just close this issue before I irritate you along with myself for the problems (and the programming incompetence on my behalf) I struggle with.
Just tried to bypass the
XmppWebSocketDecoder
Unmarshaller and create and return a manualStreamElement
i.e.StreamHeader
if receiving string contains 'stream:stream'. Just tested with a similar setup as the receiving opening stream from the server from browser login before actually trying to extract and manually add the actual 'id' (to see if the exception would change).I.e.
public final StreamElement decode(final String s) throws DecodeException { try (StringReader reader = new StringReader(s)) { String Element = IOUtils.toString(reader); if (Element.contains("stream:stream")) { if (Element.contains("/stream:stream")) { StreamHeader close = new StreamHeader.CLOSING_STREAM_TAG(); if (onRead != null) { onRead.accept(s, close); } return close; } else { StreamHeader open = "<manual stream based on web browser login>"; if (onRead != null) { onRead.accept(s, open); } return open; } } else { StreamElement streamElement = (StreamElement) unmarshaller.get().unmarshal(reader); if (onRead != null) { onRead.accept(s, streamElement); } return streamElement; } } catch (JAXBException e) { logger.warn("Decoder Failure"); throw new DecodeException(s, e.getMessage(), e); } }
However, with this code, I still get the exact same DecodeException:
rocks.xmpp.core.XmppException: javax.websocket.DecodeException: unexpected element (uri:"http://etherx.jabber.org/streams", local:"stream"). Expected elements are <{urn:xmpp:sm:3}a>,<{urn:ietf:params:xml:ns:xmpp-sasl}abort>.....
So it does not seem as it is the unmarshaller causing the problem (unless the String.contains() part is not recognized from my code).
So, going further, would you think this is a far-fetched problem to solve? I have no clue what additional problems I could meet if I actually was to solve the Websocket connection. As mentioned, I had working XMPP over BOSH connection, but not sure if the existing stanza code etc also would work over Websocket connection?
Please advice if you would recommend to stop chasing an unreachable solution.
-
Btw, what would be the expected string (regular websocket/XMPP protocol) going as argument into the
StreamElement decode
method look like?Just to know how the if should be built.
-
repo owner I think, you should debug your code and analyse some stacktrace, I can't really help here from remote. I don't even know, what libary you are using (
Element.contains()
). But theoretically is looks like a valid approach.The string for the regular WebSocket connection would be
<open ..../>
. -
Really appreciate your patience and your willingness to help.
I finally managed to adjust
XmppWebSocketDecoder
to work outsideUnmarshaller
andDecodeException
if receiving string containsstream:stream
. Meaning I extract the ID from the string and creates a newOpen
for the Websocket Model which is acknowledged by the code, and subsequent stream is flowing as it should.So, I actually manage to connect to server and login/bind, and luckily, my "old code" for XMPP over BOSH is still working as it used to as well, so I am a happy guy (and potentially also others as part of the OpenHab community).
Really appreciate your guidance.
-
repo owner Your workaround is valid, too. If you could convince your server developer to just update to the official WebSocket spec, that would be even better, so that other libraries can use that server, too, without annoying modifications.
-
repo owner - changed status to closed
- Log in to comment
Just to follow up, hopefully you´ll be able to help. Trailing the websocket solution as when logging in to IoT gateway using mac Safari / web devoloper tool, xmpp-websocket turns up as type during login (port 5280). Also, when just testing in browser, running
ipaddress:5280/xmpp-websocket/
, the browser responds "It works! Now point your WebSocket client to this URL to connect to Prosody."So in my java setup, as listed above, I am chasing the
WebSocketConfiguration & XmppClient
route. So, in addition to the code above, I have the following connection code:And as mentioned in the previous post, I end up with error:
When logging in via web browser, the following stream is established: