go-xmpp v0.4.0
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 !