Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to pass the variable to 'handler'? #198

Closed
sttyru opened this issue Jun 2, 2017 · 7 comments
Closed

How to pass the variable to 'handler'? #198

sttyru opened this issue Jun 2, 2017 · 7 comments

Comments

@sttyru
Copy link

sttyru commented Jun 2, 2017

Hello!

My issue were not necessarily linked with julienschmidt/httprouter but I sure you can to help me.
I try to write a little application at Go for simulate a behaviour of some legacy HTTP-backends. My choice fell on julienschmidt/httprouter because this tool have an impressive performance and I found any pretty examples. But I have a trouble by the one part of code.
I need to pass the variable to 'handler'. I have an huge JSON-like structure filled by a data from the database. I need to use it in whole or in part. Of cource, I can to call the function for re-filling a structure at the everyone HTTP-request but it's too slow. I found some mentions about 'wrappers' for function but I don't know how to apply this. Besides, I'm guess that not everything is what it appears on the surface.
Could you kindly share a code for newbies like a me, please? :)

Thank you!

@sttyru
Copy link
Author

sttyru commented Jun 2, 2017

I want to get something like that (it's a plot only):

func main() {
...
        s := query();
        r := httprouter.New();
        r.GET("/", GetAnIndex, s );
}
...
func GetAnIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, s *Settings) {
....
    fmt.Println(s.Path);
...
}

@falmar
Copy link

falmar commented Jun 2, 2017

Hello, are you using Go 1.7+ ? I could give you and example using the context package. And no, you cannot change the the interface type httprouter.Handle func(http.ResponseWriter, *http.Request, Params)

@sttyru
Copy link
Author

sttyru commented Jun 2, 2017

Hello, @falmar! Thank you! Can you share an example?

Yes. I'm using go 1.7.4.

go version go1.7.4 linux/amd64

@falmar
Copy link

falmar commented Jun 2, 2017

I encourage you to use the standard http.Handler func (w http.ResponseWriter, r *http.Request) instead of httprouter.Handle which may change in favor of the context package.

This would be a standard http.Handler

func getIndex(w http.ResponseWriter, r *http.Request) {
  //...

  // ugh... but here we don't have access to s
  fmt.Println(s.Path);
  
  //...
}

Well s does't exist in the scope and besides where the hell is ps httprouter.Params? lets fix that!

First you would need to wrap julien's httprouter.Handle to take the Params, put them in the context and then call your http.Handler.

The wrapper receives a http.Handler and returns a httprouter.Handle to satisfy httprouter

func wrapHandler(h http.Handler) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		// Take the context out from the request
		ctx := r.Context()

		// Get new context with key-value "params" -> "httprouter.Params"
		ctx = context.WithValue(ctx, "params", ps)

		// Get new http.Request with the new context
		r = r.WithContext(ctx)

		// Call your original http.Handler
		h.ServeHTTP(w, r)
	}
}

You now can take the params out in your GetIndex handler

func getIndex(w http.ResponseWriter, r *http.Request) {
	//...

	// You can take the params this way
	ps, ok := r.Context().Value("params").(httprouter.Params)

	if !ok {
		log.Fatal("ps is not type httprouter.Params")
	}

	//...
}

I ignore where your Settings are coming from, but there could be two approaches, use a middleware to pass the settings down the context or use a wrapper similar to the func wrapHandler

  1. Use a wrapper
func getIndexWithSettings(s Settings) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		//

		// Settings is in the scope
		fmt.Println(s.Path)

		//
	})
}

func getIndexWithSettings2(s Settings) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		//

		// Settings is in the scope and ps httprouter.params
		fmt.Println(s.Path)

		//
	}
}
  1. Use a middleware
func settingsMiddleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Take the context out from the request
		ctx := r.Context()

		// Get the settings
		s := somewhere()

		// Get new context with key-value "settings"
		ctx = context.WithValue(ctx, "settings", s)

		// Get new http.Request with the new context
		r = r.WithContext(ctx)

		// Call your original http.Handler
		h.ServeHTTP(w, r)
	})
}

This is how you would use any of this approaches

func main() {
	r := httprouter.New()

	// wrapHandler
	r.GET("/index", wrapHandler(http.HandlerFunc(getIndex)))

	// inject settings
	r.GET("/index", getIndexWithSettings(s))

	// middleware
	r.GET("/index", wrapHandler(settingsMiddleware(http.HandlerFunc(getIndex))))

        http.ListenAndServe(addr, r)
}

But chained all this handlers and wrappers will be cumbersome you could use Alice to fix that

@sttyru
Copy link
Author

sttyru commented Jun 2, 2017

@falmar
Thank you for the comprehensive answer! You are guru of Go!
But in my sorry I can't to build it :-( Help me, please, again. Sorry, I know it's rude.

package main

import (
         "fmt"         
         "context"
         "net/http"
         "github.com/julienschmidt/httprouter"
)

func main() {
            r := httprouter.New()
            var s = "/var/bin"
            r.GET("/index", getIndexWithSettings(s))          
            http.ListenAndServe(":8080", r)
}

func wrapHandler(h http.Handler) httprouter.Handle {
        return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
        // Take the context out from the request
        ctx := r.Context()
        ctx = context.WithValue(ctx, "params", ps)
        r = r.WithContext(ctx)
        h.ServeHTTP(w, r)
    }
}       

func getIndex(w http.ResponseWriter, r *http.Request) {
    ps, ok := r.Context().Value("params").(httprouter.Params)
    if !ok {
        fmt.Println("ps is not type httprouter.Params")
    }
}

func getIndexWithSettings(s string) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Println(s)
    })
}
/*
func settingsMiddleware(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := r.Context()
        var s = "/var/bin"
        ctx = context.WithValue(ctx, "params", s)
        r = r.WithContext(ctx)
        h.ServeHTTP(w, r)
    })
}
*/

Message error:

 cannot use getIndexWithSettings(s) (type http.Handler) as type httprouter.Handle in argument to r.GET

@falmar
Copy link

falmar commented Jun 2, 2017

Ok, look func getIndexWithSettings is returning http.Handler, it does not satisfy httprouter.Get(string, httprouter.Handle)

change this func

func getIndexWithSettings(s string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println(s)
	})
}

for this

func getIndexWithSettings(s string) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		fmt.Println(s)
	}
}

And you will end up with this

package main

import (
	"fmt"
	"net/http"

	"github.com/julienschmidt/httprouter"
)

func main() {
	r := httprouter.New()
	var s = "/var/bin"
	r.GET("/index", getIndexWithSettings(s))
	http.ListenAndServe(":8080", r)
}

func getIndexWithSettings(s string) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		fmt.Println(s)
	}
}

You should take a look at the middleware approach as a better practice when writing future programs

package main

import (
	"context"
	"fmt"
	"net/http"

	"github.com/julienschmidt/httprouter"
)

func main() {
	r := httprouter.New()

	index := settingsMiddleware(http.HandlerFunc(getIndex))

	r.GET("/index", wrapHandler(index))

	http.ListenAndServe(":8080", r)
}

func wrapHandler(h http.Handler) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		// Take the context out from the request
		ctx := r.Context()
		ctx = context.WithValue(ctx, "params", ps)
		r = r.WithContext(ctx)
		h.ServeHTTP(w, r)
	}
}

func settingsMiddleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var s = "/var/bin"

		ctx := r.Context()
		ctx = context.WithValue(ctx, "settings", s)
		r = r.WithContext(ctx)
		h.ServeHTTP(w, r)
	})
}

func getIndex(w http.ResponseWriter, r *http.Request) {
	s, ok := r.Context().Value("settings").(string)

	if !ok {
		fmt.Println("s is not type string")
	}

	fmt.Println(s) // "/var/bin"
}

@sttyru
Copy link
Author

sttyru commented Jun 6, 2017

Thank you very much for your help! I've modified my source code and HTTP router do everything it takes! :)
Thank you!

Now I want to force drop a connection, but that's another story.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants