Openfire Logo

Mellium: A Minimal Working Example (in Go)

Introduction

This document provides a minimal working example of a client implementation using the Mellium library, making it connect to a running Openfire server.

Topics that are covered in this document:

Background

Mellium provides a collection of Go libraries, tools, and applications related to XMPP and the Jabber network.

This guide describes how to use Mellium to connect to Openfire. It provides nothing more than a minimal working example, intended as a stepping stone to for client developers that get started with a new project.

Preparations

In this example, a client connection will be made against a running Openfire server. For ease of configuration, the 'demoboot' setup of Openfire is used.

The 'demoboot' setup of Openfire allows one to start a fresh installation of Openfire into a certain provisioned state, without running any of the setup steps. When running in 'demoboot' mode:

To start Openfire in 'demoboot' mode, you can invoke the Openfire executable using the -demoboot argument, as shown below.

Starting Openfire in 'demoboot' mode.
$ ./bin/openfire.sh -demoboot

Code

To start the project, create a file named test.go in an empty directory, and copy in the code below.

Example Mellium code
package main

import (
	"context"
	"crypto/tls"
	"log"

	"mellium.im/sasl"
	"mellium.im/xmpp"
	"mellium.im/xmpp/dial"
	"mellium.im/xmpp/jid"
	"mellium.im/xmpp/stanza"
)

type logWriter struct {
	tag string
}

func (w logWriter) Write(p []byte) (int, error) {
	log.Printf("%s %s\n", w.tag, p)
	return len(p), nil
}

// MessageBody is a message stanza that contains a body. It is normally used for
// chat messages.
type MessageBody struct {
	stanza.Message
	Body string `xml:"body"`
}

// TODO: this is just an example, don't hard code passwords!
const (
	xmppPass = "secret"
	xmppUser = "jane@example.org"
)

func main() {
	j, err := jid.Parse(xmppUser)
	if err != nil {
		log.Fatalf("error parsing XMPP address: %v", err)
	}

	d := dial.Dialer{
		// TODO: we probably don't want to disable direct TLS connections and we
		// probably want to lookup the server using SRV records normally, but this
		// is an example so we're disabling security features.
		NoLookup: true,
		NoTLS:    true,
	}
	// TODO: normally we'd want to connect to the domainpart of the user
	// (example.org in this example), but let's override that and set it to
	// "localhost" since this is an example made to run locally.
	lo := jid.MustParse("localhost")
	conn, err := d.Dial(context.TODO(), "tcp", lo)
	if err != nil {
		log.Fatalf("error dialing TCP connection: %v", err)
	}

	s, err := xmpp.NewSession(context.TODO(), j.Domain(), j, conn, 0, xmpp.NewNegotiator(func(*xmpp.Session, *xmpp.StreamConfig) xmpp.StreamConfig {
		return xmpp.StreamConfig{
			Lang: "en",
			Features: []xmpp.StreamFeature{
				xmpp.BindResource(),
				xmpp.SASL("", xmppPass, sasl.Plain),
				xmpp.StartTLS(&tls.Config{
					// TODO: this is for example purposes only. We *really* don't want to
					// do this in prod. Use a nil TLS config for sane defaults.
					InsecureSkipVerify: true,
				}),
			},
			TeeIn:  logWriter{tag: "RECV"},
			TeeOut: logWriter{tag: "SENT"},
		}
	}))
	if err != nil {
		log.Fatalf("error connecting to server: %v", err)
	}
	defer func() {
		err := s.Close()
		if err != nil {
			log.Fatalf("error closing XMPP session: %v", err)
		}
	}()

	// Encode a message to ourself.
	err = s.Encode(context.TODO(), MessageBody{
		Message: stanza.Message{
			To:   s.LocalAddr(),
			Type: stanza.ChatMessage,
		},
		Body: "Hello world!",
	})
	if err != nil {
		log.Fatalf("error sending message to self: %v", err)
	}
}

Finally, build and run the test client, using the instructions below.

Build and run Mellium test client
$ go mod init example.org/melliumexample
$ go mod tidy
$ go run .

You should see the raw XMPP exchange with the server, ending with a closing stream element.

Note that this example disables important security features. You should not use this for anything important!

Further Reading

Please use the links below to find more information.