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

That should be everything that you need to get Openfire running. Background information on the 'demoboot' mode can be found in Openfire's Demoboot Guide.

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.