How to get url param in middleware go-chi - go

I use a specific middleware for specific set of routes
r.Route("/platform", func(r chi.Router) {
r.Use(authService.AuthMiddleware)
r.Get("/{id}/latest", RequestPlatformVersion)
})
Now how can I access id url param inside this AuthMiddleware middleware
func (s *Service) AuthMiddleware(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
fmt.Println(chi.URLParam(r, "id"))
id := chi.URLParam(r, "id")
if id > 100 {
http.Error(w, errors.New("Error").Error(), http.StatusUnauthorized)
return
}
}
return http.HandlerFunc(fn)
}
However, the id param prints as an empty string even though the middleware is being ran and a specific route is being called

You put your chi.URLParam before the path param {id} and you forgot to put .ServeHTTP(w, r) at the middleware. If you don't put that thing, your request will not go inside the path inside the route.
this is the working example:
package main
import (
"fmt"
"net/http"
"github.com/go-chi/chi"
)
func AuthMiddleware(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
fmt.Println(chi.URLParam(r, "id"))
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func main() {
r := chi.NewRouter()
r.Route("/platform/{id}", func(r chi.Router) {
r.Use(AuthMiddleware)
r.Get("/latest", func(rw http.ResponseWriter, r *http.Request) {
fmt.Println("here ", chi.URLParam(r, "id")) // <- here
})
})
http.ListenAndServe(":8080", r)
}
I move the {id} to platform/{id} so the middleware got the id path value, and add h.ServeHTTP(w, r) inside the middleware.
try to access http://localhost:8080/platform/1/latest
the output will be:
1
here 1
UPDATE
It is not good to run the validation after the code, you must fix the way you define the path, and move the .ServeHTTP after the validation.
This is the example:
package main
import (
"errors"
"fmt"
"net/http"
"strconv"
"github.com/go-chi/chi"
)
func AuthMiddleware(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Middleware First, id: %+v\n", chi.URLParam(r, "id"))
id, _ := strconv.Atoi(chi.URLParam(r, "id"))
if id > 100 {
http.Error(w, errors.New("Error").Error(), http.StatusUnauthorized)
return
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func main() {
r := chi.NewRouter()
// This works too ()
// r.Route("/platform/{id}", func(r chi.Router) {
// r.Use(AuthMiddleware)
// r.Get("/latest", func(rw http.ResponseWriter, r *http.Request) {
// fmt.Println("second: ", chi.URLParam(r, "id")) // <- here
// })
// })
// Other Solution (Wrapping Middleware)
r.Route("/platform", func(r chi.Router) {
r.Get("/{id}/latest", AuthMiddleware(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
fmt.Println("second: ", chi.URLParam(r, "id")) // <- here
})).ServeHTTP)
})
http.ListenAndServe(":8080", r)
}

Related

HTTP Middleware and Google Cloud Functions

What’s the equivalent to middleware handlers in Google Cloud Functions?
In standard approach, normally I do:
router.Handle("/receive", middlewares.ErrorHandler(MyReceiveHandler))
And then, in the middleware:
type ErrorHandler func(http.ResponseWriter, *http.Request) error
func (fn ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
err := fn(w, r)
if err == nil {
return
}
log.Printf("An error accured: %v", err)
clientError, ok := err.(errors.BaseError)
if !ok {
w.WriteHeader(500)
return
}
w.WriteHeader(clientError.GetStatusCode())
w.Write([]byte(clientError.Error()))
}
In AWS Lambda, I can achieve the same thing using, for example:
func main() {
lambda.Start(
middlewares.Authentication(Handler),
)
}
But I could not find a way to do this in GCP Functions.
How would it work?
Can you help me?
Let's say you start with the following server code in your development environment:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.Handle("/", MiddlewareFinalMsg(" Goodbye!", http.HandlerFunc(HelloWorld)))
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
func MiddlewareFinalMsg(msg string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
fmt.Fprint(w, msg)
})
}
func HelloWorld(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, "Hello, World!")
}
As far as I can tell, GCF requires its entry point to be an exported identifier of type func(http.ResponseWriter, *http.Request) (not http.HandlerFunc, not http.Handler); therefore, if you have a http.Handler, you'll need to select its ServeHTTP method explicitly to obtain a function of the expected type. However, that identifier can be a package-level function, a method, or a variable.
Here is how you can adapt the code above for GCF:
package p
import (
"fmt"
"net/http"
)
// use F as your GCF's entry point
var F = MiddlewareFinalMsg(" Goodbye!", http.HandlerFunc(HelloWorld)).ServeHTTP
func MiddlewareFinalMsg(msg string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
fmt.Fprint(w, msg)
})
}
func HelloWorld(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, "Hello, World!")
}

First time chaining functions

Suppose I want to run a logger before I run my custom mux for http server.
Part way there I can chain loggers and add a custom mux like this: https://play.golang.org/p/Edurl-Rhqb9
package main
import (
"fmt"
"net/http"
"time"
"github.com/julienschmidt/httprouter"
)
type Middleware func(http.HandlerFunc) http.HandlerFunc
func ServeHTTPIterator(h http.HandlerFunc, m ...Middleware) http.HandlerFunc {
if len(m) < 1 {
return h
}
wrapped := h
// loop in reverse to preserve middleware order
for i := len(m) - 1; i >= 0; i-- {
wrapped = m[i](wrapped)
}
return wrapped
}
func IndexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello Index!")
}
func LogFirst(h http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Print(`First`)
h.ServeHTTP(w, r)
})
}
func LogSecond(h http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Print(`Second`)
h.ServeHTTP(w, r)
})
}
func main() {
httpServer := &http.Server{
Addr: `my.local:8080`,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: 5 * time.Second,
}
var Router *httprouter.Router
preMiddle := []Middleware{
LogFirst,
LogSecond,
}
http.HandleFunc("/", ServeHTTPIterator(IndexHandler, preMiddle...))
httpServer.Handler = Router
}
But now let suppose I wanted to run my mux after the loggers, in that case I figured I would do something like:
preMiddle := []Middleware{
LogFirst,
LogSecond,
Router,
}
But I'm not sure how to wrap Router to make this work or if this is even a legitimate approach. Could someone clue me in on what I am missing here?
Modify the code to work with http.Handler instead of http.HandlerFunc.
type Middleware func(http.Handler) http.Handler
func ServeHTTPIterator(h http.Handler, m ...Middleware) http.Handler {
if len(m) < 1 {
return h
}
wrapped := h
// loop in reverse to preserve middleware order
for i := len(m) - 1; i >= 0; i-- {
wrapped = m[i](wrapped)
}
return wrapped
}
func LogFirst(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Print(`First`)
h.ServeHTTP(w, r)
})
}
func LogSecond(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Print(`Second`)
h.ServeHTTP(w, r)
})
}
Create the router and register the handler with the router. Add parameters argument to IndexHandler so that the function is compatible with httprouter.
router := httprouter.New()
router.GET("/", IndexHandler)
Wrap the router using the new version of ServeHTTPIterator.
httpServer.Handler = ServeHTTPIterator(router, preMiddle...)
With these changes, the logs are written before the router is called.
You can achieve it by, instead of spreading the middlewares in your serveIterator func expect an slice of middlewares to be applied like: go playground
package main
import (
"fmt"
"net/http"
"time"
"github.com/julienschmidt/httprouter"
)
type Middleware func(http.HandlerFunc) http.HandlerFunc
func ServeHTTPIterator(h http.HandlerFunc, m []Middleware) http.HandlerFunc {
if len(m) < 1 {
return h
}
wrapped := h
// loop in reverse to preserve middleware order
for i := len(m) - 1; i >= 0; i-- {
wrapped = m[i](wrapped)
}
return wrapped
}
func IndexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello Index!")
}
func LogFirst(h http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Print(`First`)
h.ServeHTTP(w, r)
})
}
func LogSecond(h http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Print(`Second`)
h.ServeHTTP(w, r)
})
}
func main() {
httpServer := &http.Server{
Addr: `my.local:8080`,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: 5 * time.Second,
}
var Router *httprouter.Router
preMiddle := []Middleware{
LogFirst,
LogSecond,
}
http.HandleFunc("/", ServeHTTPIterator(IndexHandler, preMiddle))
httpServer.Handler = Router
}

How to access handler value globally

I have this simple http server. How can i access the request data to a global variable and use it in any part of the application.
package main
import (
"io"
"net/http"
)
var data string // Get URL data globally and use it in other part of the application
func hello(w http.ResponseWriter, r *http.Request) {
data := r.URL.Query().Get("somestring")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", hello)
http.ListenAndServe(":8000", mux)
}
You could use net/context with http.Handler. for example you have "X-Request-ID" in header, you could define middlware like this:
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
ctx := newContextWithRequestID(req.Context(), req)
next.ServeHTTP(rw, req.WithContext(ctx))
})
}
type key int
const requestIDKey key = 0
func newContextWithRequestID(ctx context.Context, req *http.Request) context.Context {
reqID := req.Header.Get("X-Request-ID")
if reqID == "" {
reqID = generateRandomID()
}
return context.WithValue(ctx, requestIDKey, reqID)
}
func requestIDFromContext(ctx context.Context) string {
return ctx.Value(requestIDKey).(string)
}
you could get requestIDKey in any handler with Context object.
func handler(rw http.ResponseWriter, req *http.Request) {
reqID := requestIDFromContext(req.Context())
fmt.Fprintf(rw, "Hello request ID %v\n", reqID)
}
this is just an example. insted of requestIDKey you could have any data which you should put in Context and read from it with a key.
for more detail information visit this blog.

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.

How can I use HttpRouter with MuxChain?

I'd like to use httprouter with muxchain while keeping route parameters like /:user/.
Take the following example:
func log(res http.ResponseWriter, req *http.Request) {
fmt.Println("some logger")
}
func index(res http.ResponseWriter, req *http.Request) {
fmt.Fprintf(res, "Hi there, I love %s!", req.URL.Path[1:])
}
func main() {
logHandler := http.HandlerFunc(log)
indexHandler := http.HandlerFunc(index)
chain := muxchain.ChainHandlers(logHandler, indexHandler)
router := httprouter.New()
router.Handler("GET", "/:user", chain)
http.ListenAndServe(":8080", router)
}
When I visit http://localhost:8080/john I obviously don't have access to ps httprouter.Params
That's because httprouter needs to see type httprouter.Handle but the function is called with type http.Handler.
Is there any way to use both packages together? The HttpRouter GitHub repo says
The only disadvantage is, that no parameter values can be retrieved when a http.Handler or http.HandlerFunc is used, since there is no efficient way to pass the values with the existing function parameters.
If you strongly want to use that packages, you can try to do something like that:
package main
import (
"fmt"
"github.com/gorilla/context"
"github.com/julienschmidt/httprouter"
"github.com/stephens2424/muxchain"
"net/http"
)
func log(res http.ResponseWriter, req *http.Request) {
fmt.Printf("some logger")
}
func index(res http.ResponseWriter, req *http.Request) {
p := context.Get(req, "params").(httprouter.Params)
fmt.Fprintf(res, "Hi there, I love %s!", p.ByName("user"))
}
func MyContextHandler(h http.Handler) httprouter.Handle {
return func(res http.ResponseWriter, req *http.Request, p httprouter.Params) {
context.Set(req, "params", p)
h.ServeHTTP(res, req)
}
}
func main() {
logHandler := http.HandlerFunc(log)
indexHandler := http.HandlerFunc(index)
chain := muxchain.ChainHandlers(logHandler, indexHandler)
router := httprouter.New()
router.GET("/:user", MyContextHandler(chain))
http.ListenAndServe(":8080", router)
}
You would have to patch muxchain to accept httprouter.Handle, but it's rather simple to create your own chain handler, for example:
func chain(funcs ...interface{}) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
for _, h := range funcs {
switch h := h.(type) {
case httprouter.Handle:
h(w, r, p)
case http.Handler:
h.ServeHTTP(w, r)
case func(http.ResponseWriter, *http.Request):
h(w, r)
default:
panic("wth")
}
}
}
}
playground

Resources