go-xmpp v0.4.0

ProcessOne
· 5 min read
Send by email

A new version of the go-xmpp library, which can be used to write XMPP clients or components in Go, has been released. It’s available on GitHub.

You can find the full changelog here: CHANGELOG
Some noteworthy features are the support of three new extensions:

— XEP-0060: Publish-Subscribe
— XEP-0050: Ad-Hoc Commands
— XEP-0004: Data Forms

for both component and client.

Callbacks for error management were also added to the client and component.

New Extensions

PubSub

Let’s create a very simple client that is both owner of a node, and subscribed to it.

This example assumes that you have a locally running jabber server listening on port 5222, like ejabberd.

First, let’s create the client. We need to provide a user JID, our identity, then the address of the server we wish to connect to.

Let’s also get a little ahead and put the node name and the service name in our constants:

const (
      domain = "mycomponent.localhost"
      address = "localhost:8888"
      nodeName = "example_node"
      serviceName = "pubsub.localhost"
      )

Now, we need to fill a Config struct, that will be passed to the NewClient method :

config := xmpp.Config{
      TransportConfiguration: xmpp.TransportConfiguration{
      Address: serverAddress,
      },
      Jid: userJID,
      Credential: xmpp.Password("pass123"), // For the sake brievety
      Insecure: true,    
    }

To process publications, we need to setup a route to catch messages and print them on screen:

router := xmpp.NewRouter();
router.NewRoute().
     Packet("message"). 
     HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {// Packet type to go through this route
      // Make the packet readable and print it
      data, _ := xml.Marshal(p)
      fmt.Println("Received a publication ! =>   n" + string(data))
  })

Let’s make the client ! (see next feature’s description for an explanation on the func(err error) argument)

 client, err := xmpp.NewClient(config, router, func(err error){ fmt.Println(err) })

We can connect:

err := c.Connect()

Our client is live, let’s make it create a node on the service, using the previously defined constants:

// Build the request
rqCreate, err := stanza.NewCreateNode(serviceName, nodeName)
// Send it  
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
ch, err := client.SendIQ(ctx, rqCreate)

Now we just have to wait for our response:

select {  
  case iqResponse := <-ch:
  // Got response from server
  fmt.Print(iqResponse.Payload)
  case <-time.After(100 * time.Millisecond):
    cancel()
    panic("No iq response was received in time")
}

The node is created! Let us subscribe to it before we publish :

// Create a subscribe request
rqSubscribe, err := stanza.NewSubRq(serviceName, stanza.SubInfo{        
  Node: nodeName,
  Jid:  userJID,
})
// Send it
pubRespCh, _ := client.SendIQ(ctx, rqSubscribe)

We can publish to that node. The publish model is very generic and can be a bit verbose, but it provides great flexibility.

Let’s say we want to publish this item:

<item id="62B5B8B3AB34">
  <entry xmlns="http://www.w3.org/2005/Atom">
    <title xmlns="http://www.w3.org/2005/Atom">My pub item title</title>
    <summary xmlns="http://www.w3.org/2005/Atom">My pub item content summary</summary>
    <link href="http://denmark.lit/2003/12/13/atom03" rel="alternate" type="text/html" xmlns="http://www.w3.org/2005/Atom"/>
    <id xmlns="http://www.w3.org/2005/Atom">My pub item content ID</id>
    <published xmlns="http://www.w3.org/2005/Atom">2003-12-13T18:30:02Z</published>
    <updated xmlns="http://www.w3.org/2005/Atom">2003-12-13T18:30:02Z</updated>
  </entry>
</item>

This is how we would need to build the request:

 pub, err := stanza.NewPublishItemRq(serviceName, nodeName, "", stanza.Item{
      Publisher: "testuser2",
      Any: &stanza.Node{
        XMLName: xml.Name{
          Space: "http://www.w3.org/2005/Atom",
          Local: "entry",
        },
        Nodes: []stanza.Node{
          {
            XMLName: xml.Name{Space: "", Local: "title"},
            Attrs:   nil,
            Content: "My pub item title",
            Nodes:   nil,
          },
          {
            XMLName: xml.Name{Space: "", Local: "summary"},
            Attrs:   nil,
            Content: "My pub item content summary",
            Nodes:   nil,
          },
          {
            XMLName: xml.Name{Space: "", Local: "link"},
            Attrs: []xml.Attr{
              {
                Name:  xml.Name{Space: "", Local: "rel"},
                Value: "alternate",
              },
              {
                Name:  xml.Name{Space: "", Local: "type"},
                Value: "text/html",
              },
              {
                Name:  xml.Name{Space: "", Local: "href"},
                Value: "http://denmark.lit/2003/12/13/atom03",
              },
            },
          },
          {
            XMLName: xml.Name{Space: "", Local: "id"},
            Attrs:   nil,
            Content: "My pub item content ID",
            Nodes:   nil,
          },
          {
            XMLName: xml.Name{Space: "", Local: "published"},
            Attrs:   nil,
            Content: "2003-12-13T18:30:02Z",
            Nodes:   nil,
          },
          {
            XMLName: xml.Name{Space: "", Local: "updated"},
            Attrs:   nil,
            Content: "2003-12-13T18:30:02Z",
            Nodes:   nil,
          },
        },
      },
    })

Then we can send it !

client.SendIQ(ctx, pub)

As we are subscribed to it, the route that we setup earlier will catch a message from the server that contains this publication and print it on screen.

You can find another use of this extension in our xmpp_jukebox example.

Full example

The full program that:

— runs a client
— connects it to a XMMP server
— creates a node, subscribes to it
— publishes to it
— prints the notification from it

is available in our library repository, with a few extras.

XEP-0050 : Ad-Hoc Commands

Using the example above, let’s say we already have a connected client.
To request all pending subscriptions requests for all nodes on a PubSub
service, we would just use :

subR, err := stanza.NewGetPendingSubRequests(serviceName)
subRCh, err := client.SendIQ(ctx, subR)

Now we just need to listen to the channel (subRCh) to get the server response.

This request uses the XEP-0050 under the hood, as specifed by XEP-0060: 8.7 Process Pending Subscription Requests.
Support for XEP-0050 is currently provided without helper functions.

XEP-0004: Data Forms

Support of this extension was added partly to cover the 8.2 Configure a Node section of XEP-0060.
In this process, the client must send a request like :

<iq type='get'    from='hamlet@denmark.lit/elsinore'    to='pubsub.shakespeare.lit'    id='config1'>
  <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
    <configure node='princely_musings'/>
  </pubsub>
</iq>

to which the server responds with a form that should be returned with the new configuration values for the node. To request that form, we would need to use :

confRq, err := stanza.NewConfigureNode(serviceName, nodeName)
confReqCh, err := client.SendIQ(ctx, confRq)

Then, catch the response from the server and extract the configuration items that you wish to update :

var fields map[string]Field
select {
  case confForm:=<-confReqCh:
    fields, err = confForm.GetFormFields()
  case <-time.After(100 * time.Millisecond):
    cancel()
    log.Fatal("No iq response was received in time")
}

Edit fields to your liking:

fields["pubsub#max_payload_size"].ValuesList[0] = "200000" 
fields["pubsub#deliver_notifications"].ValuesList[0] = "1"

and send them back to the service :

submitConf, err := stanza.NewFormSubmissionOwner(
      serviceName,
      nodeName,
      []*stanza.Field{
      fields["pubsub#max_payload_size"],
      fields["pubsub#notification_type"],
    })
client.SendIQ(ctx, submitConf)

Error callbacks

Overview

The “NewClient” function changed signature from :

func NewClient(config Config, r *Router) (c *Client, err error)

to :

func NewClient(config Config, r *Router, errorHandler func(error)) (c *Client, err error)

Meaning you can now provide an errorHandler function, that will be called, currently, when:

— A new session fails to start
— Stanzas cannot be read properly
— A stream error is recieved
— There is a problem with the underlying connexion

Simple example

Here’s an example of how to use the handler. This very simple handler just prints the error that arises:

import("gosrc.io/xmpp" "log")
func main() {  
  // Create the error handler
  errHandler := func(err error) {
    fmt.Println(e)
  }
  // Create a client
  var client *xmpp.Client
  clientCfg := xmpp.Config{
    TransportConfiguration: xmpp.TransportConfiguration{
      Address: "serverAddress",
    },
    Jid:        "myJid",
    Credential: xmpp.Password("myPassword"),
    Insecure:   true
  }
  router := xmpp.NewRouter()
  if client, err = xmpp.NewClient(clientCfg, router, errHandler); err != nil {
    log.Panicln(fmt.Sprintf("Could not create a new client : %s", err))
  }  // Connect your client  
  client.Connect()
}

That’s it !