Using mgo and writing handlers in Go with net/http

This is a short example of how to write handlers in Go and use the MongoDB mgo driver.

Assume you want to write handlers that are in a package and mount this handler under a certain prefix,
for instance a HelloHandler mounted on /hello/.

So you have a main package and a handler package, called hello.

Directory structure is

~/go/src/example/hello/
~/go/src/example/hello/main.go
~/go/src/example/hello/hello/
~/go/src/example/hello/hello/hello.go

package main

import (
	"example/hello/hello"
	"log"
	"net/http"

	"gopkg.in/mgo.v2"
)

const (
	// database name
	database = "hello"
	// collection name
	helloCollection = "messages"
	// mount point e.g. http://localhost:8080/hello/
	helloMountpoint = "/hello/"
)

func main() {
	// establish mongodb connection
	ms, e := mgo.Dial("localhost")
	if e != nil {
		log.Fatalln(e.Error())
	}
	// create options using the convenience function
	ho := hello.NewHelloHandlerOptions(ms, database, helloCollection, helloMountpoint)
	// create handler using the convenience function
	hh := hello.NewHelloHandler(ho)
	// we need to close the mgo connection when the program exits
	defer hh.Close()

	// create new mux
	mux := http.NewServeMux()
	// and handle hello.HelloHandler on "/hello/"
	mux.Handle(helloMountpoint, http.StripPrefix(helloMountpoint, hh))

	// log errors and exit on error, otherwise listen on :8080
	log.Fatal(http.ListenAndServe(":8080", mux))

}
package hello

import (
	"net/http"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

// our handler struct
type HelloHandler struct {
	options *HelloHandlerOptions
}

// the handler's options that are passed
type HelloHandlerOptions struct {
	ms     *mgo.Session
	db     string
	col    string
	prefix string
}

// the mongodb message type/struct
type Message struct {
	Id   bson.ObjectId `bson:"_id,omitempty"`
	Name string        `bson:",omitempty"`
	Text string
}

// create new hellohandler convenience function
func NewHelloHandler(options *HelloHandlerOptions) *HelloHandler {
	return &HelloHandler{
		options: options,
	}
}

// create new options convenience function
func NewHelloHandlerOptions(ms *mgo.Session, db, col, prefix string) *HelloHandlerOptions {
	return &HelloHandlerOptions{
		ms:     ms.Clone(),
		db:     db,
		col:    col,
		prefix: prefix,
	}
}

// close the mgo connection
func (h *HelloHandler) Close() {
	h.options.ms.Close()
}

// satify http.Handler interface
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		// display list of messages
		h.Get(w, r)
	case "POST":
		// process post request and create a new message if valid
		h.Post(w, r)
	default:
		http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
	}
}

// I named it Get() you could also name it ListMessages() but also rename h.Get(w, r) to h.ListMessages(w, r)
func (h *HelloHandler) Get(w http.ResponseWriter, r *http.Request) {
	// copy the session
	ms := h.options.ms.Copy()
	defer ms.Close()

	cMessage := ms.DB(h.options.db).C(h.options.col)

	// create holder for messages
	msgs := []*Message{}
	// query
	if e := cMessage.Find(nil).Sort("_id").All(&msgs); e != nil {
		http.Error(w, e.Error(), http.StatusInternalServerError)
		return
	}
	// create a template execution context
	ctx := make(map[string]interface{})
	ctx["Messages"] = msgs
	// and render the template with context
	if e := tplHelloIndex.Execute(w, ctx); e != nil {
		// or return an error if there's an error in the template
		http.Error(w, e.Error(), http.StatusInternalServerError)
	}
}

func (h *HelloHandler) Post(w http.ResponseWriter, r *http.Request) {
	name := r.FormValue("name")
	message := r.FormValue("message")
	if message == "" {
		// since we're lazy return a http error
		http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	ms := h.options.ms.Copy()
	defer ms.Close()
	cMessage := ms.DB(h.options.db).C(h.options.col)

	// create the message
	msg := new(Message)
	msg.Id = bson.NewObjectId()
	msg.Name = name
	msg.Text = message

	// insert into database, if error return error
	if e := cMessage.Insert(msg); e != nil {
		http.Error(w, e.Error(), http.StatusInternalServerError)
		return
	}

	// else redirect back to display page
	http.Redirect(w, r, h.options.prefix, 302)
}
package hello

import (
	"html/template"
)

var tplHelloIndex = template.Must(template.New("index").Parse(helloIndexstring))

const helloIndexstring = `
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Hello</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
  </head>
  <body>
	<div class="container">
	    <h1>Hello</h1>
		<h2>Leave a message</h2>
		<form method="post">
			<div class="form-group">
				<label for="name">Name</label>
				<input id="name" name="name" placeholder="Name" class="form-control">
			</div>
			<div class="form-group">
				<label for="message">Message</label>
				<textarea id="message" name="message" placeholder="Message" class="form-control" required></textarea>
			</div>
			<div class="form-group">
				<button type="submit" class="btn btn-default btn-block">Submit</button>
			</div>
		</form>
		<h2>Messages</h2>
		{{ range .Messages }}
		<div class="panel panel-default">
  			<div class="panel-heading">
		    <h3 class="panel-title">{{ .Name }} says</h3>
  			</div>
	  		<div class="panel-body">
		    	{{ .Text }}
  			</div>
			<div class="panel-footer">
				on {{ .Id.Time.Format "2 January at 3:04pm" }}
			</div>
		</div>
		{{ end }}
	</div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
  </body>
</html>
`

Run it

cd ~/go/src/example/hello
go run main.go

and navigate to http://localhost:8080/hello/

Schreib einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.