Skip to content

Instantly share code, notes, and snippets.

@unders
Last active August 18, 2022 08:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save unders/8331b3cb7cd91ad7ea9864610436aecd to your computer and use it in GitHub Desktop.
Save unders/8331b3cb7cd91ad7ea9864610436aecd to your computer and use it in GitHub Desktop.
Simple HTTP Server

A server struct

A single server structure that usually ends up looking something like this:

type server struct {
    db     *someDatabase
    router *someRouter
    email  EmailSender
}

Shared dependencies are fields of the structure

routes.go

package app

func (s *server) routes() {
    s.router.HandleFunc("/api/", s.handleAPI())
    s.router.HandleFunc("/about", s.handleAbout())
    s.router.HandleFunc("/", s.handleIndex())
}

Handlers hang off the server

HTTP handlers hang off the server:

func (s *server) handleSomething() http.HandlerFunc { ... }

Return the handler

Handler functions don’t actually handle the requests, they return a function that does.

func (s *server) handleSomething() http.HandlerFunc {
    thing := prepareThing()
    return func(w http.ResponseWriter, r *http.Request) {
        // use thing        
    }
}

Middleware are just Go functions

func (s *server) adminOnly(h http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if !currentUser(r).IsAdmin {
            http.NotFound(w, r)
            return
        }
        h(w, r)
    }
}
package app

func (s *server) routes() {
    s.router.HandleFunc("/api/", s.handleAPI())
    s.router.HandleFunc("/about", s.handleAbout())
    s.router.HandleFunc("/", s.handleIndex())
    s.router.HandleFunc("/admin", s.adminOnly(s.handleAdminIndex))
}

Request and response types can go in there too

If an endpoint has its own request and response types, usually they’re only useful for that particular handler.

func (s *server) handleSomething() http.HandlerFunc {
    type request struct {
        Name string
    }
    type response struct {
        Greeting string `json:"greeting"`
    }
    return func(w http.ResponseWriter, r *http.Request) {
        ...
    }
}

The server is testable

Our server type is very testable.

func TestHandleAbout(t *testing.T) {
    is := is.New(t)
    srv := server{
        db:    mockDatabase,
        email: mockEmailSender,
    }
    srv.routes()
    req, err := http.NewRequest("GET", "/about", nil)
    is.NoErr(err)
    w := httptest.NewRecorder()
    srv.ServeHTTP(w, req)
    is.Equal(w.StatusCode, http.StatusOK)
}

Reference

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment