Accessing HTTP Request context after handler - http

In my logging middleware (first in chain) I need to access some context that is written in some auth middleware futher down the chain and only after the handler itself is executed.
Side note: The logging middleware needs to be called first since I need to log the duration of the request including the time spend in middleware. Also the auth middleware is able to abort a request when permissions are not sufficient. in that case I need to log the failed request as well.
My problem with that is that reading the context from the http.Request pointer does not return the auth data I would expect it to have. See the example bellow:
package main
import (
"context"
"fmt"
"net/http"
"time"
)
const (
contextKeyUsername = "username"
)
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, contextKeyUsername, "user123")
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func logMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func(start time.Time) {
ctx := r.Context()
username := ctx.Value(contextKeyUsername)
if username != nil {
fmt.Printf("user %s has accessed %s, took %d\n", username,
r.URL.Path, time.Since(start).Milliseconds())
} else {
fmt.Printf("annonyous has accessed %s, took %d\n",
r.URL.Path, time.Since(start).Milliseconds())
}
}(time.Now())
next.ServeHTTP(w, r)
})
}
func welcome(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
username := ctx.Value(contextKeyUsername)
if username != nil {
fmt.Fprintf(w, fmt.Sprintf("hello %s", username.(string)))
} else {
fmt.Fprintf(w, "hello")
}
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/welcome", welcome)
chain := logMiddleware(authMiddleware(mux))
http.ListenAndServe(":5050", chain)
}
While a get request to 127.0.0.1:5050/welcome does return the expected string hello user123, the output of the log is:
annonyous has accessed /welcome, took 0
Since the request is passed along as pointer I would have expected that at the time the defer is executed, the context would contain the expected username value.
What am I missing here?

WithContext returns a shallow copy of the request, i.e. the request created by the authMiddleware is not the same request as the one from which logMiddleware is reading the context.
You could have the root middleware (in this case that would be the logMiddleware) create the context-with-value and the shallow request copy, but instead of a plain string store an non-nil pointer in the context, then have the authMiddleware use pointer indirection to assign the value to which the pointer points, then the logMiddleware, after next exits, can dereference that pointer to access that value.
And to avoid the unpleasant dereferencing, instead of a pointer to a string, you can use a pointer to a struct with a string field.
type ctxKey uint8
const userKey ctxKey = 0
type user struct{ name string }
func logMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
u := new(user)
r = r.WithContext(context.WithValue(r.Context(), userKey, u))
defer func(start time.Time) {
if u.name != "" {
fmt.Printf("user %s has accessed %s, took %s\n", u.name, r.URL.Path, time.Since(start))
} else {
fmt.Printf("annonyous has accessed %s, took %s\n", r.URL.Path, time.Since(start))
}
}(time.Now())
next.ServeHTTP(w, r)
})
}
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if u, ok := r.Context().Value(userKey).(*user); ok {
u.name = "user123"
}
next.ServeHTTP(w, r)
})
}
func welcome(w http.ResponseWriter, r *http.Request) {
if u, ok := r.Context().Value(userKey).(*user); ok && u.name != "" {
fmt.Fprintf(w, "hello %s", u.name)
} else {
fmt.Fprintf(w, "hello")
}
}
https://go.dev/play/p/N7vmjQ7iLM1

Related

Passing a string to a handler function in Go

I have a generic Webserver which I want to use on different domains / servers.
For setting up each server I simply read a JSON config file with all necessary information. One would be for example the redirect for all traffic which reaches port 80 and forward it to a TLS service. Since I don't want to make the config object global. How can I pass the content from my inputFromConfigFile to the redirectTLS function?
Here is an example:
func main(){
var inputFromConfigFile = "https://www.example.com:443"
go func() {
if err := http.ListenAndServe(":80", http.HandlerFunc(redirectTLS)); err != nil {
log.Fatalf("ListenAndServe error: %v", err)
}
}()
}
//Pass the above string to this function:
func redirectTLS(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://www.example.com:443"+r.RequestURI,http.StatusMovedPermanently)
}
You can define a custom Handler (could be implemented as a struct) as long as it matches the http.Handler interface. The config could be saved inside the Handler as a struct field.
type Handler struct {
// config goes here
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// anything that handler needs to do here
}
Example: https://pkg.go.dev/net/http#example-Handle
You can define redirectTLS as an inline closure function directly in main:
var inputFromConfigFile = "https://www.example.com:443"
go func() {
err := http.ListenAndServe(":80", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, inputFromConfigFile+r.RequestURI, http.StatusMovedPermanently)
})
if err != nil {
log.Fatalf("ListenAndServe error: %v", err)
}
}()
I would make the config object global.
Otherwise, you can define a function that accepts the config as an argument, and returns a handler function that closes over the configuration object:
var inputFromConfigFile = "https://www.example.com:443"
http.ListenAndServe(":80", createHandler(inputFromConfigFile))
// ...
func createHandler(config string) http.HandlerFunc {
return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, config+r.RequestURI,http.StatusMovedPermanently)
})
}

Go's http.MaxBytesReader, why pass in writer?

Intuitively, I would think that when you create a MaxByteReader and pass in the http.ResponseWriter, it would write out the status code for you. But that isn't the case, what does the writer actually do?
example:
func maxBytesMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 1)
next.ServeHTTP(w, r)
})
}
func mainHandler(w http.ResponseWriter, r *http.Request) {
var i interface{}
err := json.NewDecoder(r.Body).Decode(&i)
if err != nil {
fmt.Println(err.Error())
}
}
func TestMaxBytesMiddleware(t *testing.T) {
handlerToTest := maxBytesMiddleware(http.HandlerFunc(mainHandler))
req := httptest.NewRequest(http.MethodPost, "http://test.com", bytes.NewReader(json.RawMessage(`{"hello":"world"}`)))
recorder := httptest.NewRecorder()
handlerToTest.ServeHTTP(recorder, req)
if recorder.Result().StatusCode != http.StatusRequestEntityTooLarge {
t.Errorf("expected %d got %d", http.StatusRequestEntityTooLarge, recorder.Result().StatusCode)
}
}
but when this test runs I get this:
http: request body too large
--- FAIL: TestMaxBytesMiddleware (0.00s)
main_test.go:37: expected 413 got 200
if I want the desired functionality of what I thought this function did, I need to change my mainHandler to something like this:
func mainHandler(w http.ResponseWriter, r *http.Request) {
var i interface{}
err := json.NewDecoder(r.Body).Decode(&i)
if err != nil {
if err.Error() == "http: request body too large" {
w.WriteHeader(http.StatusRequestEntityTooLarge)
return
}
fmt.Println(err.Error())
}
}
So what is that writer even there for?
If the MaxBytesReader stops before reading the whole body, it sets some flags on the writer that make sure that the HTTP connection will be closed after the response is sent. Normally the server would be willing to read another request from the same connection (HTTP keepalive), but it can't do that if there are unread bits of the previous request still in the pipeline, so it has to close the connection, forcing the client to make a new connection if it wants to send more requests.
This is accomplished using the private requestTooLarge method of http.ResponseWriter.

Go race condition in timeout handler

I can see two main issues in the example code below, but I don't know how to solve them correctly.
If the timeout handler does not get the signal through the errCh that the next handler has completed or an error occured, it will reply "408 Request timeout" to the request.
The problem here is that the ResponseWriter is not safe to be used by multiple goroutines. And the timeout handler starts a new goroutine when executing the next handler.
Issues:
How to prevent the next handler from writing into the ResponseWriter when the ctx's Done channel times out in the timeout handler.
How to prevent the timeout handler from replying 408 status code when the next handler is writing into the ResponseWriter but it has not finished yet and the ctx's Done channel times out in the timeout handler.
package main
import (
"context"
"fmt"
"net/http"
"time"
)
func main() {
http.Handle("/race", handlerFunc(timeoutHandler))
http.ListenAndServe(":8080", nil)
}
func timeoutHandler(w http.ResponseWriter, r *http.Request) error {
const seconds = 1
ctx, cancel := context.WithTimeout(r.Context(), time.Duration(seconds)*time.Second)
defer cancel()
r = r.WithContext(ctx)
errCh := make(chan error, 1)
go func() {
// w is not safe for concurrent use by multiple goroutines
errCh <- nextHandler(w, r)
}()
select {
case err := <-errCh:
return err
case <-ctx.Done():
// w is not safe for concurrent use by multiple goroutines
http.Error(w, "Request timeout", 408)
return nil
}
}
func nextHandler(w http.ResponseWriter, r *http.Request) error {
// just for fun to simulate a better race condition
const seconds = 1
time.Sleep(time.Duration(seconds) * time.Second)
fmt.Fprint(w, "nextHandler")
return nil
}
type handlerFunc func(w http.ResponseWriter, r *http.Request) error
func (fn handlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
http.Error(w, "Server error", 500)
}
}
Here is a possible solution, which is based on #Andy's comment.
A new responseRecorder will be passed to the nextHandler, and the recorded response will be copied back to the client:
func timeoutHandler(w http.ResponseWriter, r *http.Request) error {
const seconds = 1
ctx, cancel := context.WithTimeout(r.Context(),
time.Duration(seconds)*time.Second)
defer cancel()
r = r.WithContext(ctx)
errCh := make(chan error, 1)
w2 := newResponseRecorder()
go func() {
errCh <- nextHandler(w2, r)
}()
select {
case err := <-errCh:
if err != nil {
return err
}
w2.cloneHeader(w.Header())
w.WriteHeader(w2.status)
w.Write(w2.buf.Bytes())
return nil
case <-ctx.Done():
http.Error(w, "Request timeout", 408)
return nil
}
}
And here is the responseRecorder:
type responseRecorder struct {
http.ResponseWriter
header http.Header
buf *bytes.Buffer
status int
}
func newResponseRecorder() *responseRecorder {
return &responseRecorder{
header: http.Header{},
buf: &bytes.Buffer{},
}
}
func (w *responseRecorder) Header() http.Header {
return w.header
}
func (w *responseRecorder) cloneHeader(dst http.Header) {
for k, v := range w.header {
tmp := make([]string, len(v))
copy(tmp, v)
dst[k] = tmp
}
}
func (w *responseRecorder) Write(data []byte) (int, error) {
if w.status == 0 {
w.WriteHeader(http.StatusOK)
}
return w.buf.Write(data)
}
func (w *responseRecorder) WriteHeader(status int) {
w.status = status
}

In go, how to inspect the http response that is written to http.ResponseWriter?

There's probably something obvious that I'm missing but I'm trying to debug the HTTP response written by my go server.
I see that there's httputil.DumpResponse available but it takes a http.Response object and what I have available is http.ResponseWriter
Is there a way to extract the http.Response from http.ResponseWriter so I can inspect the content of the response to console or log?
Context:
I'm writing a simple server-side authentication using https://github.com/RangelReale/osin and it's default example, but could not understand why the front-end (using http://ember-simple-auth.com) interprets a failed authentication (incorrect password) as success.
Here's the snippet:
r = mux.NewRouter()
r.HandleFunc("/token", func (w http.ResponseWriter, r *http.Request) {
fmt.Printf("r.HandleFunc /token\n")
resp := server.NewResponse()
defer resp.Close()
r.ParseForm()
grantType := r.FormValue("grant_type")
username := r.FormValue("username")
password := r.FormValue("password")
fmt.Printf("/token : grantType=%s username=%s password=%s\n", grantType, username, password)
if ar := server.HandleAccessRequest(resp, r); ar != nil {
if username == "user" && password == "correct-password" {
ar.Authorized = true
} else {
ar.Authorized = false
}
server.FinishAccessRequest(resp, r, ar)
}
osin.OutputJSON(resp, w, r)
// Debug - doesn't work yet
dump, err := httputil.DumpResponse(w, true)
if err != nil {
fmt.Printf("%s\n", dump)
}
});
http.Handle("/token", r)
Write to an *httptest.ResponseRecorder (which implements http.ResponseWriter) and inspect it.
Example from the package:
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
)
func main() {
handler := func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "something failed", http.StatusInternalServerError)
}
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if err != nil {
log.Fatal(err)
}
w := httptest.NewRecorder()
handler(w, req)
fmt.Printf("%d - %s", w.Code, w.Body.String())
}
Edit to answer question in comments:
If I understand your question correctly, then yes, you can make use of closures for this.
Consider the following to be your handler:
func MyHandler(w http.ResponseWriter, r *http.Request) {
// do useful stuff...
}
You could then register the following closure with your servemux to attain the desired effect:
http.HandleFunc("/my/url", func(w http.ResponseWriter, r *http.Request) {
// first call MyHandler
MyHandler(w, r)
// then log whatever you need
log.Printf("%#v\n", w)
})
If this pattern proves useful to you then you could write a higher-order method that wraps any func(http.ResponseWriter, *http.Request) in such a closure. That's a topic for itself, though.

Get gorilla/mux router current route name from middleware

Problem:
Unable to access mux.CurrentRoute(r).GetName() from middleware. (Although I had been able to access it from my middleware, I had to change the way my middleware works due to it's previous inability to access the request). So I've mucked something up and I'm not sure how to get back to a working state where I can access the route name.
Any help would be much appreciated!
Error:
runtime error: invalid memory address or nil pointer dereference
Code:
func main() {
var (
err error
r *mux.Router
devRouter *mux.Router
usersRouter *mux.Router
brandsRouter *mux.Router
)
defer db.Close()
defer store.Close()
r = mux.NewRouter()
devRouter = r.PathPrefix("/api/v1/dev").Subrouter()
usersRouter = r.PathPrefix("/api/v1/users").Subrouter()
brandsRouter = r.PathPrefix("/api/v1/brands").Subrouter()
// development endpoints
devRouter.HandleFunc("/db/seed", devDbSeed)
...
// users
usersRouter.HandleFunc("/create", usersCreateHandlerFunc).Methods("POST").Name("USERS_CREATE")
...
// brands
brandsRouter.HandleFunc("/create", brandsCreateHandlerFunc).Methods("POST").Name("BRANDS_CREATE")
...
// products
brandsRouter.HandleFunc("/{brand_id:[0-9]+}/products", brandsProductsListHandlerFunc).Methods("GET").Name("BRANDS_PRODUCTS_LIST")
...
// mwAuthorize and mwAuthenticate basically work the same
mw := []func(http.Handler) http.Handler{mwAuthenticate, mwAuthorize}
http.Handle("/", use(r, mw...))
err = http.ListenAndServe(":9000", nil)
if err != nil {
logIt(err)
}
}
func use(h http.Handler, mw ...func(http.Handler) http.Handler) http.Handler {
// exec order: mw[0],mw[1],mw[N]...
for i := len(mw) - 1; i >= 0; i-- {
h = mw[i](h)
}
return h
}
func mwAuthorize(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if true != authorize(r) {
w.WriteHeader(http.StatusForbidden)
return
} else {
next.ServeHTTP(w, r)
}
})
}
func authorize(r *http.Request) (isAuthorized bool) {
isAuthorized = false
/**
This is where it's failing!
*/
routeName := mux.CurrentRoute(r).GetName()
switch routeName {
case "USERS_CREATE":
// route-specific authorization
break
...
default:
break
}
return
}
Update (2015-01-04 # 4:49PM EST):
So after removing the middleware (or at least commenting out the section that's trying to read mux.CurrentRoute) I am able to retrieve the route name from the destination handlerfunc (ex: usersCreateHandlerFunc or brandsCreateHandlerFunc). This doesn't solve my problem (I'd still like to perform authentication/authorization in middleware as opposed to every handlerfunc), I have a hunch it's letting me know *mux.Router isn't available in my middleware until after the final .ServeHTTP call. (Or something along those lines...)
Update (2015-01-04 # 5:41PM EST):
Tried a different (albeit less-preferred) direction of using Negroni as the middleware component. Still getting nil-pointer error when I try to get mux.CurrentRoute.
Update (2015-01-04 # 6:17PM EST):
I am able to access the request (ex: r.URL) from the middleware func's, but still no luck on accessing the mux.Route (ex: mux.CurrentRoute(r)). After looking a bit more at the mux source, I think it's because the current mux context isn't getting set because the router hasn't executed the matcher yet (and therefore it doesn't know what route it's currently on until AFTER the middleware is complete). However, I'm still not sure how to either resolve this, or re-structure my code to handle this.
What about:
routeName := mux.CurrentRoute(r).GetName()
Where r is the *http.Request. Don't forget to import "github.com/gorilla/mux". Remember that in order to use this, you must give you route a name when you define it
From CurrentRoute godoc:
CurrentRoute returns the matched route for the current request, if any. This only works when called inside the handler of the matched route because the matched route is stored in the request context[...]
In your example, your chain of mwAuthenticate, mwAuthorize is attached to the route "/" without using gorilla mux. That means when the request passes your handlers, it has not passed gorilla mux router.
Try the following (your example stripped down):
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
var (
err error
r *mux.Router
devRouter *mux.Router
)
func devDbSeed(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "devDbSeed")
return
}
func main() {
r = mux.NewRouter()
devRouter = r.PathPrefix("/api/v1/dev").Subrouter()
// mwAuthorize and mwAuthenticate basically work the same
mw := []func(http.Handler) http.Handler{mwAuthenticate, mwAuthorize}
// development endpoints
devRouter.Handle("/db/seed", use(http.HandlerFunc(devDbSeed), mw...)).Name("foo")
// Send all requests into the mux router
err = http.ListenAndServe(":9000", r)
if err != nil {
log.Fatal(err)
}
}
func use(h http.Handler, mw ...func(http.Handler) http.Handler) http.Handler {
// exec order: mw[0],mw[1],mw[N]...
for i := len(mw) - 1; i >= 0; i-- {
h = mw[i](h)
}
return h
}
func mwAuthorize(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !authorize(r) {
w.WriteHeader(http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
func mwAuthenticate(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
})
}
func authorize(r *http.Request) (isAuthorized bool) {
isAuthorized = false
handlerName := "UNKNOWN"
if route := mux.CurrentRoute(r); route != nil {
routeName := route.GetName()
if routeName != "" {
handlerName = routeName
}
}
log.Println(handlerName)
switch handlerName {
case "USERS_CREATE":
// route-specific authorization
log.Println("USERS_CREATE")
break
default:
break
}
return
}
I had the same problem and I resolved in that way:
var match mux.RouteMatch
routeExists := s.Router.Match(r, &match)
if routeExists && match.Route.GetName(){
routeName := match.Route.GetName()
}
And when I defined the route I added .Name("route/:param") where route/:param is my route.

Resources