Last active
March 17, 2025 15:20
-
-
Save nmerouze/5ed810218c661b40f5c4 to your computer and use it in GitHub Desktop.
Example for "Build Your Own Web Framework in Go" articles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 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()
}
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?
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
@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 😊