Search code examples
socketsgobuffered

How to read data (xml) sent by server if it doesn't send new line


Let's say we try to communicate with a server (XMPP) which sends back XML data. We can use

conn, err := net.Dial("tcp", s.Addr+":5222")
//...
r := bufio.NewReader(conn)
//...
s, err := s.R.ReadString(10) // to read a string

But there is one problem that the server doesn't send the \10 (newline) symbol. I also tried 12 but without any luck. Same goes for readLine function as it also relies on \10. So how do I read the data sent by server? I tried using '>' as a delimiter and succeeded to receive only parts of the messages (predictable). I had an idea to loop while error is nil and use delimiter of '>' but it also didn't work. My research shown that the last symbol of the message is really '>' (62) and there is not any anything else at the end.


Solution

  • Use an xml.Decoder to read stanzas from an XMPP stream.

    conn, err := net.Dial("tcp", s.Addr+":5222")
    if err != nil {
        // handle error
    }
    dec := xml.NewDecoder(conn)
    

    Use the decoder Token method to read the root document element and to skip over character data between stanzas:

    func readStartElement(dec *xml.Decoder) (xml.StartElement, error) {
        for {
            t, err := dec.Token()
            if err != nil {
                return xml.StartElement{}, err
            }
            switch t := t.(type) {
            case xml.StartElement:
                return t, nil
            }
        }
    }
    

    Use the decoder DecodeElement method to read a stanza:

    func readStanza(dec *xml.Decoder) (interface{}, error) {
        se, err := readStartElement(dec)
        if err != nil {
            return nil, err
        }
        var v interface{}
        switch  se.Name.Space + " " + se.Name.Local {
        case "jabber:client message":
            v = &jabberMessage{} // jabberMessage is struct type defined by app for messages
        // Add other stanza types here.        
        default:
            v = &struct{}{}
        }
        if err := dec.DecodeElement(v, &se); err != nil {
            return nil, err
        }
        return v, nil
    }
    

    Type switch on the return value from readStanza to handle the different types of received stanzas.

    A client reads stanzas synchronously. Here's rough outline (ignoring authentication, etc).

    conn, err := net.Dial("tcp", s.Addr+":5222")
    if err != nil {
        // handle error
    }
    dec := xml.NewDecoder(conn)
    
    // read and discard root element
    _, err := readStartElement(dec)
    if err != nil {
         // handle error
    }
    
    // read stanzas
    for {
       v, err := readStanza(dec)
       if err != nil {
           // handle error 
           // must break out of loop on error
       }
       switch v := v.(type) {
       case *jabberMessage:
            // handle message
       case *someOtherStanzaType:
            // handle other stanza types
            // ... and so on
       }
    }