Skip to content

Instantly share code, notes, and snippets.

@nmerouze
Last active May 6, 2021 16:40
Show Gist options
  • Star 40 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save nmerouze/5ed810218c661b40f5c4 to your computer and use it in GitHub Desktop.
Save nmerouze/5ed810218c661b40f5c4 to your computer and use it in GitHub Desktop.
Example for "Build Your Own Web Framework in Go" articles
package main
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"time"
"github.com/gorilla/context"
"github.com/julienschmidt/httprouter"
"github.com/justinas/alice"
)
func recoverHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic: %+v", err)
http.Error(w, http.StatusText(500), 500)
}
}()
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func loggingHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
t1 := time.Now()
next.ServeHTTP(w, r)
t2 := time.Now()
log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t2.Sub(t1))
}
return http.HandlerFunc(fn)
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "You are on the about page.")
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome!")
}
type appContext struct {
db *sql.DB
}
func (c *appContext) authHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
authToken := r.Header.Get("Authorization")
user, err := map[string]interface{}{}, errors.New("test")
// user, err := getUser(c.db, authToken)
log.Println(authToken)
if err != nil {
http.Error(w, http.StatusText(401), 401)
return
}
context.Set(r, "user", user)
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func (c *appContext) adminHandler(w http.ResponseWriter, r *http.Request) {
user := context.Get(r, "user")
// Maybe other operations on the database
json.NewEncoder(w).Encode(user)
}
func (c *appContext) teaHandler(w http.ResponseWriter, r *http.Request) {
params := context.Get(r, "params").(httprouter.Params)
log.Println(params.ByName("id"))
// tea := getTea(c.db, params.ByName("id"))
json.NewEncoder(w).Encode(nil)
}
// We could also put *httprouter.Router in a field to not get access to the original methods (GET, POST, etc. in uppercase)
type router struct {
*httprouter.Router
}
func (r *router) Get(path string, handler http.Handler) {
r.GET(path, wrapHandler(handler))
}
func NewRouter() *router {
return &router{httprouter.New()}
}
func wrapHandler(h http.Handler) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
context.Set(r, "params", ps)
h.ServeHTTP(w, r)
}
}
func main() {
// db := sql.Open("postgres", "...")
appC := appContext{nil}
commonHandlers := alice.New(context.ClearHandler, loggingHandler, recoverHandler)
router := NewRouter()
router.Get("/admin", commonHandlers.Append(appC.authHandler).ThenFunc(appC.adminHandler))
router.Get("/about", commonHandlers.ThenFunc(aboutHandler))
router.Get("/", commonHandlers.ThenFunc(indexHandler))
router.Get("/teas/:id", commonHandlers.ThenFunc(appC.teaHandler))
http.ListenAndServe(":8080", router)
}
@trapias
Copy link

trapias commented Jun 24, 2015

@nmerouze thank you, very interesting articles and code!
I'm new to Go, so forgive what is probably a silly question: I'm trying to adapt this code to move some handlers to a different package, say I want to move the teaHandler handler to an "api" package.
Could you provide some hints on how to do this? The best result I can get is that my method is undefined 😊

@SunSparc
Copy link

@trapias if you are moving code to a different package, you will need to import that package in order to have access to it.

Something like the following:

import (
    "github.com/username/project/api" // or...
    "go-path-src-directory/project/api" // or...
    "./api"
)

func foo() {
    api.Handler()
}

@diogogmt
Copy link

@nmerouze

How do you get a reference to the http.ResponseWriter and http.Request in the loggingHandler function?


func loggingHandler(next http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        t1 := time.Now()
        next.ServeHTTP(w, r)
        t2 := time.Now()
        log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t2.Sub(t1))
    }

    return http.HandlerFunc(fn)
}

You are creating an anonymous function and then passing to http.HandlerFunc, where does the writer and request get set?

@johnwesonga
Copy link

@diogogmt

the loggingHandler is essentially middleware

func loggingHandler(next http.Handler) http.Handler {
  fn := func(w http.ResponseWriter, r *http.Request) {
      t1 := time.Now()
      next.ServeHTTP(w, r)
      t2 := time.Now()
      log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t2.Sub(t1))
}

  return http.HandlerFunc(fn)
 }

func indexHandler(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "Index Handler")
}

func main() {
  index := http.HandlerFunc(indexHandler)
  http.Handle("/", loggingHandler(index))
  log.Fatal(http.ListenAndServe(":9000", nil))
}

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