2. Who am I?
● Work at
● Go developer since 2013
● Currently building cloud based energy
optimization system for commercial buildings
(using Go)
● Joakim Gustin
3. Agenda
● The basics
● Routing
● Middleware
● Accessing “globals”
● Testing
● Tips and tricks
4. HTTP in Go
● Built in web server (net/http)
● Core piece of the Go standard library
● Can be run facing clients (even Cloudflare does)
● Handles TLS
● Automatic support for HTTP/2
12. Can we make this part simpler?
type myServer struct {}
// implementing the http.Handler interface
func (ms *myServer) ServeHTTP(rw ResponseWriter, req *Request) {
if req.method == “GET” {
rw.Write([]byte(“hello”)
}
}
13. Revisiting the go type system
type myServer struct {} // Define a type
func (s myServer) DoSomething() {} // Define a method on it
type serverList []myServer // A type can be many things
func (sl serverList) DoSomething() {} // yep, valid
// wait a minute, functions are first class citizens in Go, right?
type mathFunc func(int) int
func (mf mathFunc) DoSomething() {} // OMG, yes it’s valid
14. A function implementing an interface?!
type HandlerFunc func(http.ResponseWriter, *http.Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
19. Routing the std lib way
func editHandler(w http.ResponseWriter, r *http.Request) {
title := r.URL.Path[len("/edit/"):]
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/edit/", editHandler)
http.ListenAndServe(":8080", mux)
}
$ curl localhost:8080/edit/my-page
$ # yeah, alright, but what about
$ curl localhost:8080/edit/my-page/revision/2
20. Third party to the rescue!
func editHandler(w http.ResponseWriter, r *http.Request) {
title := chi.URLParam(r, “title”)
}
func main() {
mux := chi.NewRouter()
mux.Get("/edit/{title}", editHandler)
mux.Patch("/{month}-{day}-{year}/update", fancyPatchHandler)
http.ListenAndServe(":8080", mux)
}
$ go get github.com/go-chi/chi
21. Third party to the rescue!
func editHandler(w http.ResponseWriter, r *http.Request) {
title := r.URL.Path[len("/edit/"):]
}
func main() {
mux := chi.NewRouter()
mux.Get("/edit/", editHandler)
mux.Patch("/{month}-{day}-{year}/update", fancyPatchHandler)
http.ListenAndServe(":8080", mux)
}
$ go get github.com/go-chi/chi
33. Testing an individual handler
import “net/http/httptest”
func TestMyHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/2018-03-07", nil)
w := httptest.NewRecorder()
updateHandler(w, req)
// Assert stuff on w
}
34. Testing an entire server
import “net/http/httptest”
func TestMyHandler(t *testing.T) {
mux := myapp.NewRootMux() // Let’s pretend this exists
// Now you will get the entire chain of middleware etc
ts := httptest.NewServer(mux)
defer ts.Close()
res, _:= http.Get(ts.URL + “/edit/2018-03-07”)
// Assert stuff on res
}
41. What about HTTP status codes?
type HTTPError struct {
err error // The actual error
statusCode int
}
func (h HTTPError) Error() string {
return h.err.Error() // Just pass along the original errors message
}
type error interface {
Error() string
}
42. Handling HTTP Error
type ErrorHandlerFunc func(http.ResponseWriter, *http.Request) error
func ErrorHandler(h ErrorHandlerFunc) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := f(w, r); err != nil { // an error occurred
switch e := err.(type) {
case *HTTPError:
w.WriteHeader(e.statusCode)
default:
w.WriteHeader(http.StatusInternalServerError) // aka 500
}
}
})
}