Skip to content

Instantly share code, notes, and snippets.

@xeoncross
Last active April 30, 2021 20:22
Show Gist options
  • Save xeoncross/372bb42c24b1cb37664c377d018dd5cb to your computer and use it in GitHub Desktop.
Save xeoncross/372bb42c24b1cb37664c377d018dd5cb to your computer and use it in GitHub Desktop.
Simple example of using http middleware in Go (golang)
package main
import (
"fmt"
"log"
"net/http"
"os"
)
// Adapter wraps an http.Handler with additional
// functionality.
type Adapter func(http.Handler) http.Handler
// Adapt h with all specified adapters.
func Adapt(h http.Handler, adapters ...Adapter) http.Handler {
for _, adapter := range adapters {
h = adapter(h)
}
return h
}
// Simple logger
func Logging(l *log.Logger) Adapter {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
l.Println("Logger", r.Method, r.URL.Path)
h.ServeHTTP(w, r)
})
}
}
// Simplest handler we could write
func indexHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
w.WriteHeader(http.StatusOK)
fmt.Println("Handler", r.Method, r.URL.Path)
fmt.Fprintln(w, "Hello, World!")
})
}
func main() {
logger := log.New(os.Stdout, "", log.LstdFlags)
router := http.NewServeMux()
router.Handle("/", Adapt(indexHandler(), Logging(logger)))
err := http.ListenAndServe(":9090", router)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
/* OUTPUT:
2018/01/11 12:00:00 logger GET /
Handler GET /
*/
package main
import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"os"
)
// https://medium.com/@matryer/writing-middleware-in-golang-and-how-go-makes-it-so-much-fun-4375c1246e81
// https://medium.com/@matryer/the-http-handler-wrapper-technique-in-golang-updated-bc7fbcffa702
// https://stackoverflow.com/questions/6365535/http-handlehandler-or-handlerfunc
// https://www.nicolasmerouze.com/middlewares-golang-best-practices-examples/
// http://www.alexedwards.net/blog/making-and-using-middleware
// Validation
// https://github.com/go-playground/mold#full-example
// Allow an API handler to return anything (errors or strings or maps or structs)
// which the middleware will use to standardize the API response.
// Basically, DRY responses in JSON format for frontends.
// Adapter wraps an http.Handler with additional functionality.
type Adapter func(http.Handler, *interface{}) http.Handler
// Adapt h with all specified adapters.
func Adapt(handler interface{}, adapters ...Adapter) (h http.Handler) {
var response interface{}
switch handler := handler.(type) {
case http.Handler:
h = handler
case func(http.ResponseWriter, *http.Request):
h = http.HandlerFunc(handler)
case func(*http.Request) interface{}:
h = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
response = handler(r)
})
default:
log.Fatal("Invalid Adapt Handler", handler)
}
for _, adapter := range adapters {
h = adapter(h, &response)
}
return h
}
// Logging test here
func Logging(l *log.Logger) Adapter {
return func(h http.Handler, response *interface{}) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
l.Println("logger", r.Method, r.URL.Path)
defer func() { l.Printf("logger response %v\n", response) }()
h.ServeHTTP(w, r)
})
}
}
// API adapter
func API() Adapter {
return func(h http.Handler, response *interface{}) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
var x interface{}
switch v := (*response).(type) {
case error:
x = v.Error()
case string:
x = v
default:
x = "Couldn't figure it out"
}
json.NewEncoder(w).Encode(x)
})
}
}
// Simplest handler we could write
func apiHandler(r *http.Request) interface{} {
if r.URL.Path != "/api" {
return errors.New("API Handler Error")
}
return "Success!"
}
// Simplest handler we could write
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, World!")
}
func main() {
logger := log.New(os.Stdout, "", log.LstdFlags)
router := http.NewServeMux()
router.Handle("/api", Adapt(apiHandler, API(), Logging(logger)))
router.Handle("/invalid", Adapt(apiHandler, API(), Logging(logger)))
router.HandleFunc("/", helloHandler)
err := http.ListenAndServe(":9090", router)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
package main
import (
"encoding/json"
"errors"
"fmt"
"html/template"
"log"
"net/http"
"os"
)
// https://medium.com/@matryer/writing-middleware-in-golang-and-how-go-makes-it-so-much-fun-4375c1246e81
// https://medium.com/@matryer/the-http-handler-wrapper-technique-in-golang-updated-bc7fbcffa702
// https://stackoverflow.com/questions/6365535/http-handlehandler-or-handlerfunc
// https://www.nicolasmerouze.com/middlewares-golang-best-practices-examples/
// http://www.alexedwards.net/blog/making-and-using-middleware
// https://gist.github.com/nilium/f2ec7dcd54accd23532e82b04f1df7de
// Validation
// https://github.com/go-playground/mold#full-example
//
// https://play.golang.org/p/882ilXfpMTm
// Adapter wraps an http.Handler with additional functionality.
type Adapter func(http.Handler, *interface{}) http.Handler
// Adapt h with all specified adapters.
func Adapt(handler interface{}, adapters ...Adapter) (h http.Handler) {
var response interface{}
switch handler := handler.(type) {
case http.Handler:
h = handler
case func(http.ResponseWriter, *http.Request):
h = http.HandlerFunc(handler)
case func(*http.Request) interface{}:
h = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
response = handler(r)
})
default:
log.Fatal("Invalid Adapt Handler", handler)
}
for _, adapter := range adapters {
h = adapter(h, &response)
}
return h
}
// Allow methods to return values for
// type apiHandler interface {
// ServeHTTP(w http.ResponseWriter, r *http.Request, response *interface{})
// }
// Logging all request to this endpoint
func Logging(l *log.Logger) Adapter {
return func(h http.Handler, response *interface{}) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
l.Println("http:", r.Method, r.URL.Path, r.UserAgent())
h.ServeHTTP(w, r)
})
}
}
// Recover from Panics
func Recover(debug bool) Adapter {
return func(h http.Handler, response *interface{}) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("Caught Panic: %+v", err)
if debug {
if str, ok := err.(string); ok {
http.Error(w, str, 500)
}
return
}
http.Error(w, http.StatusText(500), 500)
}
}()
h.ServeHTTP(w, r)
})
}
}
// API adapter implments a simple version of the Google JSON styleguide
// https://google.github.io/styleguide/jsoncstyleguide.xml?showone=error#error
func API(debug bool) Adapter {
return func(h http.Handler, response *interface{}) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
w.Header().Set("Content-Type", "application/json")
var payload = make(map[string]interface{})
if e, ok := (*response).(error); ok {
fmt.Println("handler returned error", e.Error()) // debug
payload["error"] = e.Error()
} else if s, ok := (*response).(fmt.Stringer); ok {
payload["data"] = s.String()
} else {
payload["data"] = response
}
json.NewEncoder(w).Encode(payload)
})
}
}
func apiHandler(r *http.Request) interface{} {
if r.URL.Path != "/api" {
return errors.New("API Handler Error")
}
// DB response or something
return map[string]string{"a": "aa", "b": "bb"}
}
func panicHandler(w http.ResponseWriter, r *http.Request) {
panic("Unexpected panic/error!")
}
func caughtHandler(r *http.Request) interface{} {
panic("Unexpected panic/error!")
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, World!")
}
func main() {
var listenAddr = ":9090"
logger := log.New(os.Stdout, "", log.LstdFlags)
router := http.NewServeMux()
router.Handle("/api", Adapt(apiHandler, API(true), Recover(true), Logging(logger)))
router.Handle("/invalid", Adapt(apiHandler, API(true), Recover(true), Logging(logger)))
router.HandleFunc("/hello", helloHandler)
router.HandleFunc("/panic", panicHandler) // <-- Crashes the goroutine
router.Handle("/caught", Adapt(panicHandler, Recover(false)))
router.HandleFunc("/", indexHandler)
fmt.Println("started on ", listenAddr)
err := http.ListenAndServe(listenAddr, router)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
// Shows how to use templates with template functions and data
func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
// Example inline
var indexHTML = `
<ul>{{ range $value := . }}
<li><a href="{{ $value }}">{{ $value }}</a></li>
{{ end }}</ul>`
// Anonymous struct to hold template data
data := []string{
"/",
"/api",
"/invalid",
"/hello",
"/panic",
"/caught",
}
tmpl, err := template.New("index").Parse(indexHTML)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := tmpl.Execute(w, data); err != nil {
fmt.Println("Template Error", err)
}
}
//
// Custom Handlers
//
// Log2 is another middleware
// func Log2(h http.Handler) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// log.Println("Before")
// defer log.Println("After")
// h.ServeHTTP(w, r)
// })
// }
/*
func mustParams(h http.Handler, params ...string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
for _, param := range params {
if len(q.Get(param)) == 0 {
http.Error(w, "missing "+param, http.StatusBadRequest)
return // exit early
}
}
h.ServeHTTP(w, r) // all params present, proceed
})
}
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)
}
*/