Mit Googles Programmiersprache Go lassen sich dank des mitgebrachten HTTP-Pakets sehr leicht Webapplikationen oder -schnittstellen programmieren. Viele typische Aufgaben, wie etwa das Erzeugen von JSON, Parsen von Parametern oder Logging sind in wenigen Zeilen erledigt. Vor allem entsteht dabei aber ein Binärprogramm, das keine Virtual Machine benötigt und sehr ressourcensparend läuft.

In diesem Tutorial möchte ich zeigen, wie man in der Kombination von Go, Bootstrap und jQuery eine kleine, schöne Webanwendung realisiert. Beispielhaft habe ich das Monitoring von Diensten als Aufgabe gewählt.

Voraussetzungen

Für dieses Tutorial wird nur der Go-Compiler benötigt. Unter Debian/Ubuntu kann er installiert werden mit:

sudo apt-get install golang

Die benötigten JavaScript-Libraries sind im Projektarchiv des Tutorials bereits enthalten. Ich empfehle, direkt auf dieses Archiv zurückzugreifen, da wir uns nur auf den Anteil in Go konzentrieren wollen. Grundsätzliche Kenntnisse in JavaScript, AJAX mit JSON und HTML setzte ich in diesem Artikel ohnehin voraus.

Der Code

Wie schon erwähnt bezieht sich dieses Tutorial allein auf den Anteil in Go. Der in diesem Abschnitt erläuterte Quellcode befindet sich in der Datei monitor.go.

Zunächst wird das eigene Paket deklariert und die benötigten Pakete eingebunden. Besonders wichtig für dieses Tutorial sind die Pakete net/http (für den HTTP-Server) und encoding/json (En-/Decoding JSON für die Config und die AJAX-Schnittstelle).

package main

import (
	"fmt"
	"log"
	"net"
	"flag"
	"time"
	"net/http"
	"io/ioutil"
	"encoding/json"
)

Der einzige Parameter des Programms soll -config sein und einen alternativen Dateipfad für die Konfiguration entgegen nehmen können:

var configpath *string = flag.String("config", "config.json", "Use config file")

Nun folgen die Datenstrukturen: ListedService beschreibt dabei einen Dienst, der überwacht werden soll und Config die Programmeinstellungen, die unter anderem eine Liste von zu überwachenden Diensten enthält:

type ListedService struct {
	Name string
	Address string
	IsUp bool
	LastSeen string
}

type Config struct {
	BindTo string
	Interval time.Duration
	Services []ListedService
}

Für die Konfiguration folgt dann eine globale Variable:

var config Config

Die Funktion checkport versucht, sich mit einem TCP-Port eines Servers zu verbinden. Wenn das erfolgreich war, also ein TCP-Handshake stattgefunden hat, wird der Dienst als Verfügbar bewertet. Der Parameter address hat hierbei die Form „host:port“.

func checkport(address string) bool {
	conn, err := net.Dial("tcp", address)
	if err != nil {
		return false
	}
	conn.Close()
	return true
}

Da aber nicht nur ein Dienst geprüft werden soll, sondern alle in der Konfiguration gelisteten Dienste, muss das Array in der Konfiguration iteriert werden. Außerdem werden die Werte IsUp und LastSeen gesetzt. Diese Werte werden dem Benutzer bei einer Anfrage später geliefert.

func checker(t time.Time) {
	for i := range config.Services {
		// Dienst verfügbar
		if( checkport(config.Services[i].Address) ) {
			// Lognachricht bei Wechsel: Offline zu Verfügbar
			if( !config.Services[i].IsUp ) {
				log.Println("Service up: ", config.Services[i].Name, 
				            "at", config.Services[i].Address)
			}
			config.Services[i].IsUp = true
			config.Services[i].LastSeen = t.Format("02.01.2006 15:04:05")
		// Dienst nicht erreichbar
		} else {
			// Lognachricht bei Wechsel: Verfügbar zu Offline
			if( config.Services[i].IsUp ) {
				log.Println("Service down: ", config.Services[i].Name, 
				            "at", config.Services[i].Address)
			}
			config.Services[i].IsUp = false
		}
	}
}

Es sollen natürlich nicht bei jedem Seitenaufruf alle Dienste geprüft werden. Das kann zum einem auch mal länger dauern und ist auch ein Problem, wenn durch zu schnelles Nachladen zu viele Tests gestartet werden. Viel sinnvoller ist es, die Tests periodisch zu wiederholen. Die Funktion timedCheck verwendet time.Ticker, um den Test in regelmäßigen Abständen zu wiederholen und übergibt dabei die aktuelle Zeit:

func timedCheck() {
	ticker := time.NewTicker(time.Second * config.Interval)
	for t := range ticker.C {
		checker(t)
	}
}

Um die im Test ermittelten Daten anschließend in einer AJAX-Schnittstelle als JSON anzubieten, wird eine Handlerfunktion für den HTTP-Dienst geschrieben. Hier wird einfach die Liste der Servicedaten in JSON umgewandelt und gesendet. Wichtig ist, vorher den Content-Type auf „application/json“ zu setzen, da jQuery die Daten sonst nicht richtig interpretiert.

func handler(w http.ResponseWriter, r *http.Request) {
	b, err := json.Marshal(config.Services)
	if err!=nil {
		http.Error(w, "Error while creating json", 500)
	}
	w.Header().Set("Content-Type", "application/json")
	fmt.Fprintf(w, "%s", b)
}

Es fehlt nur noch die Hauptfunktion, die alles vereint. Neben den genannten Funktionen wird hierbei zusätzlich ein Dateiserver gestartet, der die im Projektordner www mitgelieferten HTML-, CSS- und JavaScript-Dateien bereitstellt.

func main() {
	// Initialisiere Parameter
	flag.Parse()

	// Lade die JSON-Kofiguration
	content, err := ioutil.ReadFile( *configpath )
	if err!=nil {
		log.Fatal( "Error - ", err )
	}
	err=json.Unmarshal([]byte(content), &config)
	if err!=nil {
		log.Fatal( "Error - ", err )
	}

	// Initiale Überprüfung und periodische Abfrage als Thread starten
	checker( time.Now() )
	go timedCheck();

	// AJAX-Handler registrieren
	http.HandleFunc("/ajax/GetStats", handler)
        // Dateiserver-Hanlder registrieren (für statische Daten)
	http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("www"))))

	log.Println("Starting monitoring server bound to", config.BindTo)
	log.Fatal( http.ListenAndServe(config.BindTo, nil) )
}

Kompilieren und Benutzung

Kompiliert und ausgeführt wird das Programm dann mit:

go build monitor.go
./monitor

Mit den Standardeinstellungen in der Konfiguration ist der Dienst danach unter http://localhost:8080/ erreichbar.

Fazit und Ausblick

Die Sprache Go bietet neben guter Performance auch mächtige Tools, mit denen in kurzer Zeit und ohne große Abhängigkeiten Webanwendungen entwickelt werden können. Das bietet sich natürlich besonders für administrative Web-basierte Dienste an, die bisher häufig mit dicken Webservern im Paket mit PHP oder Jetty realisiert sind. Dass die meisten Pakete unter der MIT-Lizenz stehen, macht die Sprache auch für kommerzielle Projekte interessant.