Header based routing with Chi - go

I'm trying to implement two routes using the Chi router. One should be invoked only if the "host" header is set to example.com. But only the lastly added route is invoked.
r := chi.NewRouter()
r.Use(middleware.Logger)
middlewareHeader := middleware.RouteHeaders().Route("Host", "example.com", middleware.New(r)).Handler
r.With(middlewareHeader).MethodFunc("get", "/", func(w http.ResponseWriter, r *http.Request) {
log.Println("Host: example.com")
echo(w, r)
})
middlewareNone := middleware.RouteHeaders().Handler
r.With(middlewareNone).MethodFunc("get", "/", func(w http.ResponseWriter, r *http.Request) {
echo(w, r)
})
srv := &http.Server{
Handler: r,
Addr: "127.0.0.1:8080",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())

As mentioned on comment: you cannot assign multiple handlers to one route.
RouteHeaders is a neat little header-based router that allows you to direct the flow of a request through a middleware stack based on a request header.
RouteHeaders is used to put your request through specific middleware stack, not to change routes. You can still use it but middleware has to redirect to other route.
Options:
1. Create second route and redirect in middleware.
func redirectOnHeader(header, value string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get(header) == value {
http.Redirect(w, r, "/with-header", http.StatusMovedPermanently)
return
}
next.ServeHTTP(w, r)
})
}
}
then call the middleware on desired route
r := chi.NewRouter()
r.With(redirectOnHeader("Host", "example.com")).Get("/", func(w http.ResponseWriter, r *http.Request) {
echo(w, r)
})
r.Get("/with-headers", func(w http.ResponseWriter, r *http.Request) {
echo(w, r)
})
2. Use second router and RouteHeaders
As in example in docs.
Note that you need 2 routers on order to have 2 "/" routes.
r := chi.NewRouter()
anotherR := chi.NewRouter()
r.Use(middleware.RouteHeaders().
Route("Host", "example.com", middleware.New(r)).
RouteDefault(middleware.New(anotherR)).
Handler)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
echo(w, r)
})
anotherR.Get("/", func(w http.ResponseWriter, r *http.Request) {
echo(w, r)
})
3. Implement two logic in one HandlerFunc
func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get(header) == value {
// do something
return
}
// do something else
})

Related

Gorilla mux json header for all routes golang

Is there a way to set json header to all routes?
func Ping(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
json.NewEncoder(rw).Encode(map[string]string{"Status": "OK"})
}
func Lol(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
json.NewEncoder(rw).Encode(map[string]string{"Status": "OK"})
}
not to duplicate this
json.NewEncoder(rw).Encode(map[string]string{"Status": "OK"})
You can use middleware to add Content-Type: application/json header to each handler
func contentTypeApplicationJsonMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
next.ServeHTTP(w, r)
})
}
Then register the middleware to gorilla/mux as below
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Use(contentTypeApplicationJsonMiddleware)

How to use gorilla middleware handlers for all requests?

I want to use the handlers specified here for logging everything.
This is what I have:
r := mux.NewRouter()
s := r.PathPrefix("/api/v1").Subrouter()
s.HandleFunc("/abc", handler.GetAbc).Methods("GET")
s.HandleFunc("/xyz", handler.GetXyz).Methods("GET")
I want to use the logging middleware but I don't want to repeat it in every single line, as they show in github:
r.Handle("/admin", handlers.LoggingHandler(os.Stdout, http.HandlerFunc(ShowAdminDashboard)))
r.HandleFunc("/", ShowIndex)
Is there a way to just pass the general logging middleware to r, and everything that passes the r router will pass by the middleware first?
Use a middleware:
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do stuff here
log.Println(r.RequestURI)
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
})
}
r.Use(loggingMiddleware)
Here's the doc: https://github.com/gorilla/mux#middleware
I wrapped the LoggingHandler with a middleware function
func loggingMiddleware(next http.Handler) http.Handler {
return handlers.LoggingHandler(os.Stdout, next)
}
r.Use(loggingMiddleware)
This is the approach I took and this worked best for me.
type Route struct {
Name string
Method string
Pattern string
Secure bool
HandlerFunc http.HandlerFunc
}
type Routes []Route
var routes = Routes{
Route{
Name: "Docs",
Method: "GET",
Pattern: "/v2/docs",
HandlerFunc: Docs,
},
Route{
Name: "GetUserByName",
Method: "GET",
Pattern: "/v2/user/{username}",
HandlerFunc: user.GetUserByName,
Secure: true,
},
}
func NewRouter() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
router.NotFoundHandler = http.HandlerFunc(notFound)
router.MethodNotAllowedHandler = http.HandlerFunc(notAllowed)
for _, route := range routes {
var handler http.Handler
if route.Secure {
handler = AuthMiddleware(route.HandlerFunc)
} else {
handler = route.HandlerFunc
}
handler = Logger(os.Stderr, handler)
router.
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(handler)
}
return router
}
func ApplicationRecovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
fmt.Fprintln(os.Stderr, "Recovered from application error occurred")
_, _ = fmt.Fprintln(os.Stderr, err)
w.WriteHeader(http.StatusInternalServerError)
}))
}
}()
next.ServeHTTP(w, r)
})
}
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
next.ServeHTTP(w, r)
})
}
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//TODO: Add authentication
log.Println("Authentication required")
next.ServeHTTP(w, r)
})
}
func Logger(inner http.Handler, name string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf(
"%s %s %s %s",
r.Method,
r.RequestURI,
name,
time.Since(start),
)
inner.ServeHTTP(w, r)
})
}
func notFound(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}
func notAllowed(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed)
}
func main() {
srv := http.Server{
Addr: "0.0.0.0:8080",
Handler: ApplicationRecovery(Middleware(NewRouter())),
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
This way I'm covering my bases:
panic during the request execution
a common middleware that set the response headers for all request
a logging middleware for all request logging
an authentication middleware for secure resources
Console logs of the handler in action
2020/06/23 22:28:48 Server started
2020/06/23 22:28:51 Authentication required
2020/06/23 22:28:51 Begin x-api-key validation
2020/06/23 22:28:51 x-api-key matched user: 1
2020/06/23 22:28:51 User 1 successfully accessed secure resourecs
::1 - - [23/Jun/2020:22:28:51 +0100] "DELETE /v2/user/john?permanent=true HTTP/1.1" 403 85
More compact solution
r.Use(func(next http.Handler) http.Handler { return handlers.LoggingHandler(os.Stdout, next) })

How to get response header from reverse proxy

How can I detect and react on a response header from within a reverse proxy?
I'm writing a middleware method which is injected into a reverse proxy. I confirmed the middleware is being called.
The server sets a response header X-SERVER-VAR with a value of Foo. I need to detect and run some code based on the value.
I thought I'd read the ResponseWriter, thinking the server response should include the value, but nothing is logging.
func SessionHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf(w.Header().Get("X-SERVER-VAR"))
next.ServeHTTP(w, r)
})
}
I'm using github.com/gorilla/mux to attach this middleware handler.
I'm setting everything up with code similar to below (simplified for this question).
func newProxy(remote string) http.Handler {
proxyurl, err := url.Parse(remote)
if err != nil {
panic(err)
}
proxy := httputil.NewSingleHostReverseProxy(proxyurl)
return proxy
}
func main() {
r := mux.NewRouter()
r.Use(SessionHandler)
proxy := newProxy("https://www.example.com/")
r.PathPrefix("/").Handler(proxy)
log.Fatal(http.ListenAndServe(":9001", r))
}
If it isn't possible with middleware, is it possible to detect a response header from the server another way?
You can't expect to read the response headers before they're set.
Change this:
func SessionHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf(w.Header().Get("X-SERVER-VAR"))
next.ServeHTTP(w, r)
})
}
To this:
func SessionHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
log.Printf(w.Header().Get("X-SERVER-VAR"))
})
}

Go Gorilla Mux MiddlewareFunc with r.Use and returning errors

How do you set up Gorilla Mux r.Use to return errors down the middleware chain? https://godoc.org/github.com/gorilla/mux#Router.Use
Main.go
r := mux.NewRouter()
r.Use(LoggingFunc)
r.Use(AuthFunc)
Basic middleware
Starts with logging middleware which can catch and handle errors from further down the chain
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error
func LoggingFunc(next HandlerFunc) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Logging middleware
defer func() {
if err, ok := recover().(error); ok {
w.WriteHeader(http.StatusInternalServerError)
}
}()
err := next(w, r)
if err != nil {
// log error
}
})
}
The next middleware handles authentication and returns an error to the logging middleware.
func AuthFunc(next HandlerFunc) HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
if r.GET("JWT") == "" {
return fmt.Errorf("No JWT")
}
return next(w, r)
}
}
I keep getting errors like
cannot use AuthFunc (type func(handlers.HandlerFunc) http.Handler) as type mux.MiddlewareFunc in argument to r.Use
Thanks
According to the mux.Use doc its argument type is MiddlewareFunc which return type is http.Handler not error type. You have to define which return type is http.HandlerFunc
type Middleware func(http.HandlerFunc) http.HandlerFunc
func main() {
r := mux.NewRouter()
// execute middleware from right to left of the chain
chain := Chain(SayHello, AuthFunc(), LoggingFunc())
r.HandleFunc("/", chain)
println("server listening : 8000")
http.ListenAndServe(":8000", r)
}
// Chain applies middlewares to a http.HandlerFunc
func Chain(f http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {
for _, m := range middlewares {
f = m(f)
}
return f
}
func LoggingFunc() Middleware {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Loggin middleware
defer func() {
if _, ok := recover().(error); ok {
w.WriteHeader(http.StatusInternalServerError)
}
}()
// Call next middleware/handler in chain
next(w, r)
}
}
}
func AuthFunc() Middleware {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("JWT") == "" {
fmt.Errorf("No JWT")
return
}
next(w, r)
}
}
}
func SayHello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello client")
}
It will execute the LogginFunc then AuthFunc and then SayHello method which is your desire method after passing all those middlewares.

net/http renders the root route even when I go to a route which doesn't exist

I have these routes:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
renderAndExecuteTemplate(w, r, "page/index.tmpl", nil)
})
http.HandleFunc("/route1", func(w http.ResponseWriter, r *http.Request) {
renderAndExecuteTemplate(w, r, "page/route1.tmpl", nil)
})
http.HandleFunc("/route2", func(w http.ResponseWriter, r *http.Request) {
renderAndExecuteTemplate(w, r, "page/route2.tmpl", nil)
})
It works.
However, when I go to a route which doesn't exist: "localhost/fdsafdsafdsfds", it still renders the "index" page.
Why? How to prevent it from that?
From the docs:
Note that since a pattern ending in a slash names a rooted subtree,
the pattern "/" matches all paths not matched by other registered
patterns, not just the URL with Path == "/".
One way to prevent this is to build a handler that looks at the request:
http.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
if r.URL.Path != "/" {
w.WriteHeader(http.StatusNotFound)
return
}
renderAndExecuteTemplate(w, r, "page/index.tmpl", nil)
})

Resources