ruk·si

Go
HTTP Server

Updated at 2014-01-22 00:53

Default HTTP server. Here is an example HTTP server that tells you what time it is. By calling the default functions of net/http package (e.g. HandleFunc here), we are using default values in that package e.g. DefaultServeMux.

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {
    http.HandleFunc("/", indexHandler)
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        panic(err)
    }
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Time is %s.", time.Now())
}

Custom HTTP server. Here is more verbose version of the previous minimalistic HTTP server that illustrates what is happening behind the scenes.

package main

import (
    "fmt"
    "net"
    "net/http"
    "time"
)

func main() {
    // Create new ServeMux to handle routing HTTP request to the right handlers.
    mux := http.NewServeMux()
    mux.HandleFunc("/", indexHandler)

    // Create new HTTP Server.
    srv := http.Server{Addr: ":8080", Handler: mux}

    // Start listening local TCP connections on the port specified in server.
    listener, errListen := net.Listen("tcp", srv.Addr)
    if errListen != nil {
        panic(errListen)
    }

    // Link the listener with the server, start accepting incoming connections
    // from listener and creating new handler goroutine for each connection.
    errServe := srv.Serve(listener)
    if errServe != nil {
        panic(errServe)
    }
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Time is %s.", time.Now())
    // "Forward Print Formatted" to ResponseWriter w.
}

Make at least port configurable. Command-line arguments or a config file.

package main

import (
    "flag"
    "fmt"
    "net/http"
    "time"
)

var port int

// init() is always ran when a package is ran or imported.
// main() is only ran when when a package is ran.
func init() {
    // $ go build server.go
    // $ server_executable -help
    // Usage of /server_executable:
    // -port=8080: HTTP server port.
    // $ server_executable
    // Listening to :8080.
    // $ server_executable -port 80
    // Listening to :80.
    // OR:
    // go run server.go -port 8090
    flag.IntVar(&port, "port", 8080, "HTTP server port.")
    flag.Parse() // Look for all defined flags in command-line arguments.
}

func main() {
    http.HandleFunc("/", indexHandler)
    portWithColon := fmt.Sprintf(":%d", port)
    fmt.Printf("Listening to %s.\n", portWithColon)
    err := http.ListenAndServe(portWithColon, nil)
    if err != nil {
        panic(err)
    }
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Time is %s.", time.Now())
}

Serve directories. Serve a static directory that would include CSS/JS.

package main

import (
    "flag"
    "fmt"
    "net/http"
    "time"
)

var (
    staticDir string
    staticUrl string
)

func init() {
    // Usage:
    // go run server.go -staticdir="./teststatic" -staticurl="/public/"
    flag.StringVar(&staticDir, "staticdir", "./static", "Serve this directory.")
    flag.StringVar(&staticUrl, "staticurl", "/static/", "Serve to this URL.")
    flag.Parse()
}

func main() {
    http.HandleFunc("/", indexHandler)

    // Recursively serve the given directory.
    fileServer := http.FileServer(http.Dir(staticDir))
    fileHandler := http.StripPrefix(staticUrl, fileServer)
    http.Handle(staticUrl, fileHandler)

    portWithColon := fmt.Sprintf(":%d", 8080)
    fmt.Printf("Listening to %s.\n", portWithColon)
    err := http.ListenAndServe(portWithColon, nil)
    if err != nil {
        panic(err)
    }
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Time is %s.", time.Now())
}

Redirects. Redirect handler to /redirect-me URL.

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {
    http.HandleFunc("/", indexHandler)

    // Redirect visitors to given URL using given HTTP code.
    http.Handle("/redirect-me", http.RedirectHandler("http://google.com", 302))

    portWithColon := fmt.Sprintf(":%d", 8080)
    fmt.Printf("Listening to %s.\n", portWithColon)
    err := http.ListenAndServe(portWithColon, nil)
    if err != nil {
        panic(err)
    }
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Time is %s.", time.Now())
}

Add timeout to a specific handler.

package main

import (
    "flag"
    "fmt"
    "net/http"
    "time"
)

const (
    timeoutDur = time.Duration(time.Second)
    timeoutMsg = "Your request has timed out."
)

var (
    port      int
    staticDir string
    staticUrl string
)

type BadIndexHandler struct{}

func (self *BadIndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    time.Sleep(timeoutDur)
    fmt.Fprintf(w, "Time is %s.", time.Now())
}

func init() {
    flag.IntVar(&port, "port", 8080, "HTTP server port.")
    flag.StringVar(&staticDir, "staticdir", "./static", "Serve this directory.")
    flag.StringVar(&staticUrl, "staticurl", "static", "Serve to this URL.")
    flag.Parse()
    staticUrl = fmt.Sprintf("/%s/", staticUrl)
}

func main() {

    // http.TimeoutHandler() runs given handler (here BadIndexHandler) for
    // given duration and fails if not completed in that time.
    indexHandler := &BadIndexHandler{}
    http.Handle("/", http.TimeoutHandler(indexHandler, timeoutDur, timeoutMsg))

    fileSrv := http.FileServer(http.Dir(staticDir))
    staticHandler := http.StripPrefix(staticUrl, fileSrv)
    http.Handle(staticUrl, staticHandler)

    http.Handle("/redirect-me", http.RedirectHandler("http://google.com", 302))

    portWithColon := fmt.Sprintf(":%d", port)
    fmt.Printf("Listening to %s.\n", portWithColon)
    err := http.ListenAndServe(portWithColon, nil)
    if err != nil {
        panic(err)
    }
}

Add serving JSON data and manipulating headers.

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "net/http"
    "strconv"
    "time"
)

var (
    port      int
    staticDir string
    staticUrl string
)

func init() {
    flag.IntVar(&port, "port", 8080, "HTTP server port.")
    flag.StringVar(&staticDir, "staticdir", "./static", "Serve this directory.")
    flag.StringVar(&staticUrl, "staticurl", "static", "Serve to this URL.")
    flag.Parse()
    staticUrl = fmt.Sprintf("/%s/", staticUrl)
}

func main() {
    http.HandleFunc("/", indexHandler)
    http.HandleFunc("/json", jsonHandler)
    http.HandleFunc("/header", headerHandler)

    fileSrv := http.FileServer(http.Dir(staticDir))
    staticHandler := http.StripPrefix(staticUrl, fileSrv)
    http.Handle(staticUrl, staticHandler)

    http.Handle("/redirect-me", http.RedirectHandler("http://google.com", 302))

    portWithColon := fmt.Sprintf(":%d", port)
    fmt.Printf("Listening to %s.\n", portWithColon)
    err := http.ListenAndServe(portWithColon, nil)
    if err != nil {
        panic(err)
    }
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Time is %s.", time.Now())
}

func jsonHandler(w http.ResponseWriter, r *http.Request) {
    encoder := json.NewEncoder(w)
    respData := struct {
        ServerPort int
        StaticDir  string
        StaticUrl  string
        Timestamp  time.Time
    }{}
    respData.ServerPort = port
    respData.StaticDir = staticDir
    respData.StaticUrl = staticUrl
    respData.Timestamp = time.Now()
    if err := encoder.Encode(&respData); err != nil {
        http.Error(w, fmt.Sprintf("Cannot encode response data: %v", err), 500)
    }
}

func headerHandler(w http.ResponseWriter, r *http.Request) {
    ua := r.Header.Get("User-Agent")
    msg := fmt.Sprintf("Your user agent is: %v\n", ua)
    w.Header().Set("Content-Type", "text/plain")
    w.Header().Set("Content-Length", strconv.Itoa(len(msg)))
    w.Header().Set("Go-Timestamp", fmt.Sprintf("%s.", time.Now()))
    fmt.Fprint(w, msg)
}

Cookies. Adding and reading cookies.

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "net/http"
    "strconv"
    "time"
)

var (
    port      int
    staticDir string
    staticUrl string
)

func init() {
    flag.IntVar(&port, "port", 8080, "HTTP server port.")
    flag.StringVar(&staticDir, "staticdir", "./static", "Serve this directory.")
    flag.StringVar(&staticUrl, "staticurl", "static", "Serve to this URL.")
    flag.Parse()
    staticUrl = fmt.Sprintf("/%s/", staticUrl)
}

func main() {
    http.HandleFunc("/", indexHandler)
    http.HandleFunc("/json", jsonHandler)
    http.HandleFunc("/header", headerHandler)
    http.HandleFunc("/add-cookie", addCookieHandler)
    http.HandleFunc("/read-cookie", readCookieHandler)

    fileSrv := http.FileServer(http.Dir(staticDir))
    staticHandler := http.StripPrefix(staticUrl, fileSrv)
    http.Handle(staticUrl, staticHandler)

    http.Handle("/redirect-me", http.RedirectHandler("http://google.com", 302))

    portWithColon := fmt.Sprintf(":%d", port)
    fmt.Printf("Listening to %s.\n", portWithColon)
    err := http.ListenAndServe(portWithColon, nil)
    if err != nil {
        panic(err)
    }
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Time is %s.", time.Now())
}

func jsonHandler(w http.ResponseWriter, r *http.Request) {
    encoder := json.NewEncoder(w)
    respData := struct {
        ServerPort int
        StaticDir  string
        StaticUrl  string
        Timestamp  time.Time
    }{}
    respData.ServerPort = port
    respData.StaticDir = staticDir
    respData.StaticUrl = staticUrl
    respData.Timestamp = time.Now()
    if err := encoder.Encode(&respData); err != nil {
        http.Error(w, fmt.Sprintf("Cannot encode response data: %v", err), 500)
    }
}

func headerHandler(w http.ResponseWriter, r *http.Request) {
    ua := r.Header.Get("User-Agent")
    msg := fmt.Sprintf("Your user agent is: %v\n", ua)
    w.Header().Set("Content-Type", "text/plain")
    w.Header().Set("Content-Length", strconv.Itoa(len(msg)))
    w.Header().Set("Go-Timestamp", fmt.Sprintf("%s.", time.Now()))
    fmt.Fprint(w, msg)
}

func addCookieHandler(w http.ResponseWriter, r *http.Request) {
    cookie := &http.Cookie{
        Name:  "cookie-name",
        Value: "cookie-value",
    }
    http.SetCookie(w, cookie)
}

func readCookieHandler(w http.ResponseWriter, r *http.Request) {
    if cookie, err := r.Cookie("cookie-name"); err != nil {
        http.Error(w, "Failed to read the cookie.", 500)
    } else {
        fmt.Fprintf(w, "Cookie value is %s.", cookie.Value)
    }
}

Communication between requests. As each requests starts new goroutine, it is not safe to change any public variables, prefer using channels.

package main

import (
    "fmt"
    "net/http"
    "time"
)

type indexHandler struct {
    visitChan chan bool
}

func (self *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    self.visitChan <- true
    fmt.Fprintf(w, "Time is %s.", time.Now())
}

func main() {
    var visitChan = make(chan bool)
    go countVisits(visitChan)
    http.Handle("/", &indexHandler{visitChan})
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        panic(err)
    }
}

func countVisits(visitChan chan bool) {
    var visits int
    for {
        if <-visitChan {
            visits++
            fmt.Printf(" %d visits\n", visits)
        }
    }
}

Sources