If I were to use the DefaultServeMux (which I designate by passing nil as the second argument to ListenAndServe), then I have access to http.HandleFunc, which you see used below in this example from the Go wiki:
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
In my current code, I am not able to use the DefaultServeMux i.e. I'm passing a custom handler to ListenAndServe
h := &mypackage.Handler{
Database: mydb
}
http.ListenAndServe(":8080", h)
so I don't get the http.HandleFunc built in. However, I have to adapt some authorization code to my code base that requires something like http.HandleFunc. For example, if I had been using DefaultServeMux, when I hit the "/protected" route, I would want to go to the Protected handler, but only after passing through the h.AuthorizationHandlerFunc like this
h.AuthorizationHandlerFunc(Protected)
However, since I'm not using DefaultServeMux, it's not working i.e. I'm not able to pass the Protected function (and have it called) to the AuthorizationHandlerFunc. This is the implementation of the AuthorizationHandlerFunc below. You can see below that Protected never gets called.
Question: how do I implement HandlerFunc in this situation (without using DefaultServeMux)?
func (h *Handler) AuthorizationHandlerFunc(next http.HandlerFunc) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
h.AuthorizationMiddleWare(w, r, next)
})
}
func (h *Handler) AuthorizationMiddleWare(w http.ResponseWriter, r *http.Request, next http.HandlerFunc){
//other stuff happens
log.Println("this is never getting called")
next(w,r)
}
func (h *Handler)Protected(w http.ResponseWriter, r *http.Request){
log.Println("this is never getting called")
}
Update
ServeHTTP is implemented on mypackage.Handler. Why is the Protected function not getting called, or, for that matter, the relevant code in the AuthorizationMiddleWare?
Re-implement your authorization middleware as a http.Handler :
type auth struct {
DB *sql.DB
UnauthorizedHandler http.Handler
}
func NewAuth(db *sql.DB, unauthorized http.Handler) *auth {
return auth{db, unauthorized}
}
func (a *auth) Protected(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
// Check whether the request is valid
// If it's invalid, call your error func and make sure to *return* early!
if !valid {
a.UnauthorizedHandler.ServeHTTP(w, r)
return
}
// Call the next handler on success
h.ServeHTTP(w, r)
return
}
return http.HandlerFunc(fn)
}
func someHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello!\n")
}
func main() {
auth := NewAuth(db, errorHandler)
r := http.NewServeMux()
// We have a http.Handler implementation that wraps a http.HandlerFunc
// ... so we call r.Handle on our ServeMux and type-cast the wrapped func
r.Handle("/protected", auth.Protected(http.HandlerFunc(someHandler)))
// Just a simple http.HandlerFunc here
r.HandleFunc("/public", someOtherHandler)
log.Fatal(http.ListenAndServe(":8000", r))
}
Take a look at the httpauth lib I wrote for a different example with a ServeHTTP method. Both the above and explicitly creating a ServeHTTP method on your type are valid approaches.
Related
I already have a web server and it work fine, now I want to add the fileSystem module to it.
OverView of the project under below.
// Custom router
type Handler func(ctx context.Context, w http.ResponseWriter, r *http.Request) error
type Middleware func(Handler) Handler
type App struct {
mux *chi.Mux
och *ochttp.Handler
mw []Middleware
}
func NewApp(mw ...Middleware) *App {
app := App{
mux: chi.NewRouter(),
mw: mw,
}
app.och = &ochttp.Handler{
Handler: app.mux,
Propagation: &tracecontext.HTTPFormat{},
}
return &app
}
func (a *App) Handle(verb, path string, handler Handler, mw ...Middleware) {
handler = wrapMiddleware(a.mw, handler)
h := func(w http.ResponseWriter, r *http.Request) {
...
}
a.mux.MethodFunc(verb, path, h)
}
func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
a.och.ServeHTTP(w, r)
}
// route
func API(...) http.Handler {
app := web.NewApp(mid.Logger(log)...)
s := Something{
...
}
app.Handle("GET", "/v1/somthing", s.DoSomething)
return app
}
// Handler
type Something struct {
...
}
func (s *Something) DoSomething(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
...
}
// main
api := http.Server{
...
Handler: API(...),
}
go func() {
api.ListenAndServe()
}()
The app struct is a custom router, it contains 3rd router, trace library and some middleware. The type Handler is the specific Handler format using for Middleware and any api handler register to this router. Because this project hard code only one Handler, what's the best to add another Handler like fileSystem to it?
http.Handler and Handler conversion through closure.
// http.Handler to Handler
func NewHandler(h http.Handler) Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
h.ServeHTTP(w, r)
}
}
// Handler to http.Handler
func NewHTTPHandler(h Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h(context.Background(), w, r)
})
}
// or Handler implement http.Handler
func (fn Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fn(context.Background(), w, r)
}
app.Handle("ANY","/fs/*", NewHandler(http.FileServer(http.Dir("public"))))
type Handler func (Context) is the best Handler definition. Request the context.Context object through http.Request.Context() or the top-level context.Context is obtained through the http.Server.BaseContext property.
Middleware's best implementation idea has two methods used by the echo or gin framework, but the echo method should not copy the memory allocation waste.
It is recommended to implement the router by yourself. In the mainstream framework and routing inventory, does not have routing priority, such as a large number of libraries such as echo gin httprouter gorilla/mux.
The following is the framework and the simplest implementation that I designed for 22 months.
my web framework: https://github.com/eudore/eudore
simple framework: https://github.com/eudore/eudore/wiki/3.2-frame-mirco-web
I have this utility:
type Handler struct{}
func (h Handler) Mount(router *mux.Router, v PeopleInjection) {
router.HandleFunc("/api/v1/people", h.makeGetMany(v)).Methods("GET")
}
the above calls this:
func (h Handler) makeGetMany(v PeopleInjection) http.HandlerFunc {
type RespBody struct {}
type ReqBody struct {
Handle string
}
return tc.ExtractType(
tc.TypeList{ReqBody{},RespBody{}},
func(w http.ResponseWriter, r *http.Request) {
// ...
})
}
and then tc.ExtractType is like so:
func ExtractType(s TypeList, h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r) // <<< h is just a func right? so where does ServeHTTP come from?
}
}
my question is - where does the serveHTTP method/func come from??
isn't the h parameter just a func with this signature:
func(w http.ResponseWriter, r *http.Request) { ... }
so then how does that func have the ServeHTTP func attached to it?
In other words, why I am I calling
h.ServeHTTP(w,r)
instead of
h(w,r)
?
The http.HandlerFunc is a type represent func(ResponseWriter, *Request).
The difference between http.HandlerFunc and func(ResponseWriter, *Request) is: http.HandlerFunc type has method called ServeHTTP().
From the source code:
// 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)
}
The http.HandlerFunc() can be used to wrap handler function.
func Something(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// do something
next.ServeHTTP(w, r)
})
}
The wrapped handler will have http.HandlerFunc() type, meaning we'll be able to access it's .ServeHTTP() method.
There is also another type, an interface called http.Handler. It has .ServeHTTP() method signature, and it must be implemented on the struct where the interface is being embedded.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
As you can see on Something() function above, a return value in http.Handler type is required, but we returned a handler wrapped in http.HandlerFunc(). It's fine, because the http.HandlerFunc has method .ServeHTTP() who fulfil the requirement of http.Handler interface.
In other words, why I am I calling h.ServeHTTP(w,r) instead of h(w,r)?
Because to continue serve the incoming request, you need to call .ServeHTTP().
Problem statement
I want to tie the lifetime of an HTTP request to a context that was created outside the scope of the web application. Thus, I wrote the following middleware (using github.com/go-chi/chi):
func BindContext(c context.Context) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r.WithContext(c))
})
}
}
The middleware is used in the following minimal test case:
package main
import (
"context"
"net/http"
"github.com/SentimensRG/ctx"
"github.com/SentimensRG/ctx/sigctx"
"github.com/go-chi/chi"
)
func greet(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func BindContext(c context.Context) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r.WithContext(c))
})
}
}
func main() {
r := chi.NewMux()
r.Use(BindContext(ctx.AsContext(sigctx.New())))
r.Get("/", greet)
http.ListenAndServe(":8080", r)
}
The handler panics with the following error:
2018/07/25 14:58:57 http: panic serving [::1]:56527: interface conversion: interface {} is nil, not *chi.Context
goroutine 35 [running]:
net/http.(*conn).serve.func1(0xc42014a0a0)
/usr/local/go/src/net/http/server.go:1726 +0xd0
panic(0x12749c0, 0xc42014c200)
/usr/local/go/src/runtime/panic.go:502 +0x229
github.com/go-chi/chi.(*Mux).routeHTTP(0xc4201180c0, 0x12fcf00, 0xc420166000, 0xc420160200)
/Users/lthibault/go/src/github.com/go-chi/chi/mux.go:400 +0x2f3
github.com/go-chi/chi.(*Mux).(github.com/go-chi/chi.routeHTTP)-fm(0x12fcf00, 0xc420166000, 0xc420160200)
/Users/lthibault/go/src/github.com/go-chi/chi/mux.go:368 +0x48
net/http.HandlerFunc.ServeHTTP(0xc420142010, 0x12fcf00, 0xc420166000, 0xc420160200)
/usr/local/go/src/net/http/server.go:1947 +0x44
main.fail.func1.1(0x12fcf00, 0xc420166000, 0xc420160100)
/Users/lthibault/go/src/github.com/lthibault/mesh/cmd/scratch/main.go:22 +0x77
net/http.HandlerFunc.ServeHTTP(0xc420148000, 0x12fcf00, 0xc420166000, 0xc420160100)
/usr/local/go/src/net/http/server.go:1947 +0x44
github.com/go-chi/chi.(*Mux).ServeHTTP(0xc4201180c0, 0x12fcf00, 0xc420166000, 0xc420160000)
/Users/lthibault/go/src/github.com/go-chi/chi/mux.go:81 +0x221
net/http.serverHandler.ServeHTTP(0xc420150000, 0x12fcf00, 0xc420166000, 0xc420160000)
/usr/local/go/src/net/http/server.go:2694 +0xbc
net/http.(*conn).serve(0xc42014a0a0, 0x12fd1c0, 0xc42014c080)
/usr/local/go/src/net/http/server.go:1830 +0x651
created by net/http.(*Server).Serve
/usr/local/go/src/net/http/server.go:2795 +0x27b
Inelegant solution
The problem appears to come from Mux.routeHTTP, where an attempt is made to recover a *chi.Context from r.Context(). It would appear that r.WithContext does not transfer values stored in the request context to the new context.
The obvious (albeit ugly) fix is:
func BindContext(c context.Context) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rctx := r.Context().Value(chi.RouteCtxKey).(*chi.Context)
c = context.WithValue(c, chi.RouteCtxKey, rctx)
h.ServeHTTP(w, r.WithContext(c))
})
}
}
This works, but leaves me feeling uneasy. Do I really need to manually transfer each relevant value from r.Context() into the context being passed to r.WithContext()?
There are several failure cases, here:
What happens when there are many different values to be transferred?
What happens when the context keys aren't exported (as is recommended in Go)?
What happens if the original context terminates before the one I passed in?
(In few words: nothing good!)
My question is as follows
Is there a standard "merge" a context passed to r.WithContext with the exiting context in r.Context?
You should not replace the context on incoming request with an unrelated context. For starters:
Package context defines the Context type, which carries deadlines, cancelation signals, and other request-scoped values across API boundaries and between processes.
sigctx.New() is called before any request happens and is therefore by definition not request scoped. Pretty much all code expects the request context to be canceled when a) the request finishes, or b) the client aborts the request (usually because it is no longer interested in the response). You are breaking that assumption by replacing the context. You are also removing any values that other middlewares may have added to the context earlier.
It seems like you wish to abort requests on SIGINT or SIGTERM. You should add that cancelation condition to the request context instead of replacing it completely. Perhaps like so:
func BindContext(c context.Context) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rCtx := r.Context()
ctx, cancel := context.WithCancel(rCtx)
go func() {
select {
case <-c.Done(): // SIGINT/SIGTERM
case <-rCtx.Done(): // Request finished or client aborted
}
cancel()
}()
h.ServeHTTP(w, r.WithContext(ctx))
})
}
}
Update:
To let users configure the context, accept a function that derives a new context from the request context (although users might as well supply a middleware that does this directly):
func WithContext(new func(context.Context) context.Context) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r = r.WithContext(new(r.Context()))
h.ServeHTTP(w, r)
})
}
}
It appears as though there is no out-of-the-box solution for this, but github.com/SentimensRG/ctx provides a mergectx subpackage specifically for this purpose.
The solution is to use mergectx.Merge.
I faced the same problem and was able to resolve this by creating the new context using chi.NewRouteContext.
The request is being made using httptest. You can update the request its context using r.WithContext.
Example
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("key", "value")
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
handler := func(w http.ResponseWriter, r *http.Request) {
key := chi.URLParam(r, "key") // "value"
}
handler(w, r)
See the folling Gist from aapolkovsky: https://gist.github.com/aapolkovsky/1375348cab941e36c62da24a32fbebe7
I'm coming from node express, and I was able to pass in as many middleware as possible, for example: routes.use('/*', ensureAuth, logImportant, ... n);
How can I do something similar when using r.GET("/", HomeIndex)?
Am I forced to do something like EnsureAuth(HomeIndex)? Because I can get that to work. Unfortunately, I'm not sure what would be a good way to add as many middlewares as I want without chaining functions together.
Is there a more elegant way so I could somehow use variadic type function to do r.GET("/", applyMiddleware(HomeIndex, m1, m2, m3, m4)? I'm trying that out right now, but I feel like there's a better way to do this.
I've looked at the httprouter issues page, can't find anything :(
Thanks!
Here is an example how I did it:
package main
import (
"context"
"fmt"
"log"
"net/http"
"github.com/julienschmidt/httprouter"
"github.com/justinas/alice"
)
// m1 is middleware 1
func m1(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//do something with m1
log.Println("m1 start here")
next.ServeHTTP(w, r)
log.Println("m1 end here")
})
}
// m2 is middleware 2
func m2(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//do something with m2
log.Println("m2 start here")
next.ServeHTTP(w, r)
log.Println("m2 end here")
})
}
func index(w http.ResponseWriter, r *http.Request) {
// get httprouter.Params from request context
ps := r.Context().Value("params").(httprouter.Params)
fmt.Fprintf(w, "Hello, %s", ps.ByName("name"))
}
// wrapper wraps http.Handler and returns httprouter.Handle
func wrapper(next http.Handler) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
//pass httprouter.Params to request context
ctx := context.WithValue(r.Context(), "params", ps)
//call next middleware with new context
next.ServeHTTP(w, r.WithContext(ctx))
}
}
func main() {
router := httprouter.New()
chain := alice.New(m1, m2)
//need to wrap http.Handler to be compatible with httprouter.Handle
router.GET("/user/:name", wrapper(chain.ThenFunc(index)))
log.Fatal(http.ListenAndServe(":9000", router))
}
Link to code (you can't run it from play.golang.org though): https://play.golang.org/p/BOCt97xcoY
There seem to be all sorts of examples of using a HandlerFunc closure similar to this one: http://codegangsta.gitbooks.io/building-web-apps-with-go/content/controllers/README.html
However I can't get it to work with a subrouter. Example:
func MyHandler(renderer *render.Render) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
renderer.HTML(rw, http.StatusOK, "subroute/index", nil)
})
}
func main() {
renderer := render.New(render.Options{Layout: "base"})
router := mux.NewRouter().StrictSlash(false)
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
...
})
subroutes := router.Path("/subroute").Subrouter()
subroutes.Methods("GET").HandlerFunc(MyHandler(renderer))
http.Handle("/", router)
log.Println("Listening...")
http.ListenAndServe(":3000", nil)
}
Gives me this error:
cannot use MyHandler(renderer) (type http.Handler) as type func(http.ResponseWriter, *http.Request) in function argument
Any insights into what I'm doing wrong?
The HandlerFunc method on Route expects to be passed a function, as the error message indicates. If instead you have an http.Handler, call Handler instead:
subroutes.Methods("GET").Handler(MyHandler(renderer))
Or alternatively, have your MyHandler function return the handler function directly rather than wrapping it as an http.Handler. Which option you choose is going to be a matter of style, and depend on the rest of your program.