gogo-chi

Header based routing with Chi


I'm trying to implement two routes using the Chi router. One should be invoked only if the "host" header is set to example.com. But only the lastly added route is invoked.

r := chi.NewRouter()
r.Use(middleware.Logger)

middlewareHeader := middleware.RouteHeaders().Route("Host", "example.com", middleware.New(r)).Handler
r.With(middlewareHeader).MethodFunc("get", "/", func(w http.ResponseWriter, r *http.Request) {
    log.Println("Host: example.com")
    echo(w, r)
})

middlewareNone := middleware.RouteHeaders().Handler
r.With(middlewareNone).MethodFunc("get", "/", func(w http.ResponseWriter, r *http.Request) {
    echo(w, r)
})

srv := &http.Server{
    Handler:      r,
    Addr:         "127.0.0.1:8080",
    WriteTimeout: 15 * time.Second,
    ReadTimeout:  15 * time.Second,
}

log.Fatal(srv.ListenAndServe())

Solution

  • As mentioned on comment: you cannot assign multiple handlers to one route.

    RouteHeaders is a neat little header-based router that allows you to direct the flow of a request through a middleware stack based on a request header.

    RouteHeaders is used to put your request through specific middleware stack, not to change routes. You can still use it but middleware has to redirect to other route.

    Options:


    1. Create second route and redirect in middleware.

    func redirectOnHeader(header, value string) func(http.Handler) http.Handler {
        return func(next http.Handler) http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                if r.Header.Get(header) == value {
                    http.Redirect(w, r, "/with-header", http.StatusMovedPermanently)
                    return
                }
                next.ServeHTTP(w, r)
            })
        }
    }
    

    then call the middleware on desired route

    r := chi.NewRouter()
    
    r.With(redirectOnHeader("Host", "example.com")).Get("/", func(w http.ResponseWriter, r *http.Request) {
            echo(w, r)
    })
    r.Get("/with-headers", func(w http.ResponseWriter, r *http.Request) {
            echo(w, r)
    })
    

    2. Use second router and RouteHeaders

    As in example in docs.

    Note that you need 2 routers on order to have 2 "/" routes.

    r := chi.NewRouter()
    anotherR := chi.NewRouter()
    
    r.Use(middleware.RouteHeaders().
        Route("Host", "example.com", middleware.New(r)).
        RouteDefault(middleware.New(anotherR)).
        Handler)
    
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        echo(w, r)
    })
    anotherR.Get("/", func(w http.ResponseWriter, r *http.Request) {
        echo(w, r)
    })
    

    3. Implement two logic in one HandlerFunc

    func(w http.ResponseWriter, r *http.Request) {
        if r.Header.Get(header) == value {
            // do something
            return
        }
        // do something else
    })