notes blog about

net/http

Handler interface

package http

type Handler interface {
	ServeHTTP(w ResponseWriter, r *Request)
}

func ListenAndServe(address string, h Handler) error

ListenAndServe

// Shop - a super simple e-shop showing price of shoes and socks
package main

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

func main() {
	db := database{"shoes": 50, "socks": 5}
	log.Fatal(http.ListenAndServe(":8000", db))
}

type database map[string]dollars

func (db database) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	for item, price := range db {
		fmt.Fprintf(w, "%s: %s\n", item, price)
	}
}

type dollars float32

func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }

A more realistic server triggers different behaviours based on the path component of the URL:

// Shop v2
func (db database) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case "/list":
		for item, price := range db {
			fmt.Fprintf(w, "%s: %s\n", item, price)
		}
	// called like: /price?item=socks
	case "/price":
		item := r.URL.Query().Get("item")
		price, ok := db[item]
		if !ok {
			w.WriteHeader(http.StatusNotFound) // 404
			fmt.Fprintf(w, "no such item: %q\n", item)
			return
		}
		fmt.Fprintf(w, "%s\n", price)
	default:
		w.WriteHeader(http.StatusNotFound) // 404
		fmt.Fprintf(w, "no such page: %s\n", r.URL)
	}
}
msg := fmt.Sprintf("no such page: %s\n", r.URL)
http.Error(w, msg, http.StatusNotFound) // 404

ServeMux struct

// Shop v3
func main() {
	db := database{"shoes": 50, "socks": 5}
	mux := http.NewServeMux()
	mux.Handle("/list", http.HandlerFunc(db.list))
	mux.Handle("/price", http.HandlerFunc(db.price))
	log.Fatal(http.ListenAndServe("localhost:8000", mux))
}

func (db database) list(w http.ResponseWriter, r *http.Request) {
	for item, price := range db {
		fmt.Fprintf(w, "%s: %s\n", item, price)
	}
}

func (db database) price(w http.ResponseWriter, r *http.Request) {
	item := r.URL.Query().Get("item")
	price, ok := db[item]
	if !ok {
		w.WriteHeader(http.StatusNotFound) // 404
		fmt.Fprintf(w, "no such item: %q\n", item)
		return
	}
	fmt.Fprintf(w, "%s\n", price)
}
func main() {
	db := database{"shoes": 50, "socks": 5}
	mux := http.NewServeMux()
	mux.HandleFunc("/list", db.list)
	mux.HandleFunc("/price", db.price)
	log.Fatal(http.ListenAndServe("localhost:8000", mux))
}
func main() {
	db := database{"shoes": 50, "socks": 5}
	http.HandleFunc("/list", db.list)
	http.HandleFunc("/price", db.price)
	log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

NOTE: the web server invokes each handler in a new goroutine, so handlers must take precautions such as locking when accessing variables that other goroutines, including other requests to the same handler, may be accessing.

Response handling

WriteHeader method of ResponseWriter interface

These two handler functions are equivalent:

func ok1(w http.ResponseWriter, r *http.Request) {
}

func ok2(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK) // 200
}

both generate line of information with protocol and status code (Go doc calls this line response header) plus two headers:

< HTTP/1.1 200 OK
< Date: Sat, 16 May 2020 15:17:55 GMT
< Content-Length: 0
< 

Error function

func err1(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusInternalServerError) // 500
}

generates only line of information and two headers:

< HTTP/1.1 500 Internal Server Error
< Date: Sat, 16 May 2020 15:25:28 GMT
< Content-Length: 0
< 
func err2(w http.ResponseWriter, r *http.Request) {
	http.Error(w, "500 something's wrong with the server", http.StatusInternalServerError)
}

generates also response body:

< HTTP/1.1 500 Internal Server Error
< Content-Type: text/plain; charset=utf-8
< X-Content-Type-Options: nosniff
< Date: Sat, 16 May 2020 15:29:09 GMT
< Content-Length: 38
< 
500 something's wrong with the server

Sources