How to get response header from reverse proxy - go

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"))
})
}

Related

What means foo()(bar) in go and how it works?

In auth0 QuickStart tutorial for Golang I had found this piece of code:
router.Handle("/api/private", middleware.EnsureValidToken()(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message":"Hello from a private endpoint! You need to be authenticated to see this."}`))
}),
))
Then I had simplify it to this form:
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message":"Hello from test"}`))
}
func preHandler() func(next http.Handler) http.Handler {
log.Println("in preHandler")
return func(next http.Handler) http.Handler {
return check(next)
}
}
func main() {
http.Handle("/test/", preHandler()(http.HandlerFunc(handler)))
http.ListenAndServe(":80", nil)
}
But I can't figure out how is working and what is meaning of this piece of code preHandler()(http.HandlerFunc(handler))
Thnx for help!
P.s.
I tried to find answer in google but nothing. I just want to find out how it works
The statement
http.Handle("/test/", preHandler()(http.HandlerFunc(handler)))
has three function calls and a type conversion. I'll explain what's going on by splitting the one statement into four statements:
f := preHandler() // f is function with type func(next http.Handler) http.Handler
h := http.HandlerFunc(handler) // type conversion to http.HandlerFunc
hw := f(h) // call f with h, result is an http.Handler
http.Handle("/test/", hw) // register the handler

Why does sharing state via Context only work with my request middleware?

I've been struggling with sharing state from some middleware.
Previously in Golang I've setup a "handler struct" and used functions of the definition func (h handler) doSomething to share that information in that struct. This paradigm has served me well, except it doesn't work in mux request middleware.
func (h handler) loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logging := log.WithFields(log.Fields{"requestID": r.Header.Get("X-Request-Id")})
h.Log = logging
next.ServeHTTP(w, r)
})
}
However using Context does.
func (h handler) loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logging := log.WithFields(log.Fields{"requestID": r.Header.Get("X-Request-Id")})
ctx := context.WithValue(r.Context(), logger, logging)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Full code can be found upon https://github.com/kaihendry/apex-request-id
Why though? Have I incorrectly been using this handler approach to sharing state, and I should be using context instead? Why doesn't the handler "request scoped"? (not sure on the terminologies here)

How to use middlewares when using julienschmidt/httprouter in goLang?

What's the best way to chain middlewares while using julienschmidt/httprouter?
As far as I have googled, http.HandlerFunc accepts functions only in the form func (w ResponseWriter, r *Request) whereas httprouter.Handle functions are of the form func (w http.ResponseWriter, r *http.Request, ps httprouter.Params).
How to I chain middlewares without converting the httprouter.Handle function into http.HandlerFunc?
For example:
My routes.go is of the form,
router := httprouter.New()
router.POST("/api/user/create", middlewares.EscapeStringsMiddleware(User.CreateUser))
log.Fatal(http.ListenAndServe(":8000", router))
How do I write middleware functions for the above mentioned route?
Already tried methods:
1.
func EscapeStringsMiddleware(next http.Handler) httprouter.Handle {
return func (response http.ResponseWriter, request *http.Request, ps httprouter.Params) {
err := request.ParseForm()
if err != nil {
panic(err)
}
for key, values := range request.Form {
for i, value := range values {
value = template.HTMLEscapeString(value)
value = template.JSEscapeString(value)
request.Form[key][i] = value
}
}
next.ServeHTTP(response, request)
}
}
Error obtained:
cannot use User.CreateUser (type func(http.ResponseWriter, *http.Request, httprouter.Params)) as type http.Handler in argument to middlewares.EscapeStringsMiddleware:
func(http.ResponseWriter, *http.Request, httprouter.Params) does not implement http.Handler (missing ServeHTTP method)
2.
func EscapeStringsMiddleware(next httprouter.Handle) httprouter.Handle {
return func (response http.ResponseWriter, request *http.Request, ps httprouter.Params) {
err := request.ParseForm()
if err != nil {
panic(err)
}
for key, values := range request.Form {
for i, value := range values {
value = template.HTMLEscapeString(value)
value = template.JSEscapeString(value)
request.Form[key][i] = value
}
}
next.ServeHTTP(response, request)
}
}
Error obtained:
next.ServeHTTP undefined (type httprouter.Handle has no field or method ServeHTTP)
Also how do I chain multiple middleware?
For example,
router.POST("/api/user/create", middlewares.VerifyCSRF(middlewares.EscapeStringsMiddleware(User.CreateUser)))
This issue is not with your middleware handler. You are getting errs because User.CreateUser is not of type http.Handler.
Try this pattern :
The important bit is to return a http.Handler and wrap func(w http.ResponseWriter, r *http.Request) with http.HandlerFunc.
func Handler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// do stuff
})
}
go source :
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
Based on your feedback:
httprouter.Handle does not implement ServeHTTP. It is called directly. For example : next(w, r, ps)
Below you will find examples of middleware handlers.
// Middleware without "github.com/julienschmidt/httprouter"
func StdToStdMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// do stuff
next.ServeHTTP(w, r)
})
}
// Middleware for a standard handler returning a "github.com/julienschmidt/httprouter" Handle
func StdToJulienMiddleware(next http.Handler) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// do stuff
next.ServeHTTP(w, r)
}
}
// Pure "github.com/julienschmidt/httprouter" middleware
func JulienToJulienMiddleware(next httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// do stuff
next(w, r, ps)
}
}
func JulienHandler() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// do stuff
}
}
func StdHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// do stuff
})
}
func main() {
router := httprouter.New()
router.POST("/api/user/create", StdToJulienMiddleware(StdHandler()))
router.GET("/api/user/create", JulienToJulienMiddleware(JulienHandler()))
log.Fatal(http.ListenAndServe(":8000", router))
}
From https://github.com/cnvrtly/adaptr lib where adapters are also added to chain request middleware functions.
func compatibleHandler(h http.Handler, httprParamsCtxKey interface{}) httprouter.Handle {
return toHttpRouterHandle(h, httprParamsCtxKey)
}
func toHttpRouterHandle(h http.Handler, httprParamsCtxKey interface{}) func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
if httprParamsCtxKey != nil {
r = SetCtxValue(r, httprParamsCtxKey, p)
}
h.ServeHTTP(w, r)
}
}
If you are looking for example/idea how to define routes: https://github.com/cnvrtly/golang-appengine-seed

Middleware using Alice and HttpRouter

I can't seem to work out how to use middleware and Http Router properly together.
My code is:
type appContext struct {
db *mgo.Database
}
func main(){
c := appContext{session.DB("db-name")}
commonHandlers := alice.New(context.ClearHandler, basicAuthHandler)
router := NewRouter()
router.Post("/", commonHandlers.ThenFunc(c.final))
http.ListenAndServe(":5000", router)
}
The final middleware is:
func (c *appContext) final(w http.ResponseWriter, r *http.Request) {
log.Println("Executing finalHandler")
w.Write([]byte("TESTING"))
}
but I want my basicAuthHandler to be part of the commonHandlers. It also needs the context so that I can query the db.
I have tried this:
func (c *appContext) basicAuthHandler(w http.ResponseWriter, r *http.Request) {
var app App
err := c.db.C("apps").Find(bson.M{"id":"abcde"}).One(&app)
if err != nil {
panic(err)
}
//do something with the app
}
but I get the error undefined: basicAuthHandler. I understand why I'm getting the error but I don't know how to avoid it. How can I provide the context to the basicAuthHandler and still use it in the commonHandlers list for Alice?
Your middleware needs to have the signature
func(http.Handler) http.Handler
This way your middleware is wrapping handlers, not just providing a final handler. You need to accept an http.Handler, do whatever processing needs to be done, and call ServeHTTP on the next handler in the chain. Your basicAuthHandler example could look like this:
func (c *appContext) basicAuthHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var app App
err := c.db.C("apps").Find(bson.M{"id": "abcde"}).One(&app)
if err != nil {
panic(err)
}
h.ServeHTTP(w, r)
})
}
(though you don't want to panic in your app, and should provide a better error response)

Golang net/http and Gorilla: run code before handler

Is it possible using the net/http package and/or any of the gorilla libraries to make some code execute on EVERY URL before going to the handler? For example, to check if a connection is coming from a black listed IP address?
Create a handler that invokes another handler after checking the IP address:
type checker struct {
h http.Handler
}
func (c checker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if blackListed(r.RemoteAddr) {
http.Error(w, "not authorized", http.StatusForbidden)
return
}
c.h.ServeHTTP(w, r)
}
Pass this handler to ListenAndServe instead of your original handler. For example, if you had:
err := http.ListenAndServe(addr, mux)
change the code to
err := http.ListenAndServe(addr, checker{mux})
This also applies to all the variations of ListenAndServe. It works with http.ServeMux, Gorilla mux and other routers.
If you want to use the default muxer, which i find is common, you can create middleware like so:
func mustBeLoggedIn(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// Am i logged in?
if ...not logged in... {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
// Pass through to the original handler.
handler(w, r)
}
}
Use it like so:
http.HandleFunc("/some/priveliged/action", mustBeLoggedIn(myVanillaHandler))
http.ListenAndServe(":80", nil)

Resources