Last active
April 30, 2021 20:22
-
-
Save xeoncross/372bb42c24b1cb37664c377d018dd5cb to your computer and use it in GitHub Desktop.
Simple example of using http middleware in Go (golang)
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 ( | |
"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 / | |
*/ |
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 ( | |
"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) | |
} | |
} |
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 ( | |
"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) | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Based on