go-chi / chi

lightweight, idiomatic and composable router for building Go HTTP services
https://go-chi.io
MIT License
18.54k stars 988 forks source link

Subdomain example in route_headers.go not working #691

Open jaytxrx opened 2 years ago

jaytxrx commented 2 years ago

I implemented the subdomain example provided in the route_headers.go file. Unfortunately the example provided is not working. The issue is that sub.example.com goes to the main domain page instead of sub domain page. Can anyone please let me know how to fix the issue ? Or if the below code is working for them ?

I was testing on my local Ubuntu PC and following are the contents of /etc/hosts $ cat /etc/hosts 127.0.0.1 localhost 127.0.0.1 example.com 127.0.0.1 sub.example.com

package main

import (
    "fmt"
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

func main() {
    r := chi.NewRouter()
    rSubdomain := chi.NewRouter()

    r.Use(middleware.RouteHeaders().
        Route("Host", "example.com", middleware.New(r)).
        Route("Host", "*.example.com", middleware.New(rSubdomain)).
        Handler)

    r.Get("/", h)
    rSubdomain.Get("/", h2)

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

func h(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Main domain")
}

func h2(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Sub domiain")
}
pkieltyka commented 2 years ago

at a quick glance, this looks good. maybe the wildcard check doesn't work? try to change "*.example.com" with "sub.example.com" and see if that solves it?

jaytxrx commented 2 years ago

I tried with sub.example.com. But still have the issue.

While debugging the code (route_headers.go), I found that the header information is not available in r.Header.Get(header) Below fix (line 91) helped me to get the header details. headerValue := r.Host //r.Header.Get(header)

Also I noticed that my browser is not sending the header details. I had to use curl to send header info.

After this the subdomain part works. But when accessing the main domain, the app crashes. I think still there are issues (maybe with the pattern functions). It would be a great help if someone can test the subdomain route header example and fix the issue.

pkieltyka commented 2 years ago

please paste sample / minimal main.go program with the issue (with the crash)

jaytxrx commented 2 years ago

I have pasted below the code. Note that the behavior from CURL and browser are different. Crash report from the app when using CURL is shown at the end.

1) Fix done in route_headers.go line 91 is shown below headerValue := r.Host //r.Header.Get(header)

2) My subdomain code is shown below

package main

import (
    "fmt"
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

func main() {
    r := chi.NewRouter()
    rSubdomain := chi.NewRouter()

    r.Use(middleware.RouteHeaders().
        Route("Host", "example.com", middleware.New(r)).
        Route("Host", "sub.example.com", middleware.New(rSubdomain)).
        Handler)

    r.Get("/", h)
    rSubdomain.Get("/", h2)

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

func h(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Main domain")
}

func h2(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Sub domiain")
}

3) First I will start with the browser responses. Irrespective of sub.example.com or example.com, the response is always "Main domain". Even though the browser is sending the Host (see the attached images) it seems that the go application is not getting the Host details and hence always providing the same response. sub.example.com example.com

4) CURL responses

$ curl --header 'Host: sub.example.com' 'sub.example.com:8080'
Sub domiain
curl --header 'Host: .example.com' 'example.com:8080'
Main domain
curl --header 'Host: example.com' 'example.com:8080'
curl: (52) Empty reply from server

While sending 'Host: example.com', the app crashed. Crash report is here

js-everts commented 2 years ago

The RouteHeaders middleware can't be used to for Host matching. The example in route_headers.go should be updated to reflect this or exclude the example entirely.

From https://pkg.go.dev/net/http

For incoming requests, the Host header is promoted to the Request.Host field and removed from the Header map.

In the meantime you can try https://github.com/go-chi/hostrouter

js-everts commented 2 years ago

Also in the example the router (r) seems to be mounted onto itself which is what's causing the stack overflow.

FelicianoTech commented 1 year ago

Just wanted to add my two cents here. I followed the example as well and also couldn't get it to work. As js-everts mentioned, I don't believe it will work based on the code it's using.

I also tried using hostrouter and couldn't get that to work either. I was about to give up until I realized that:

  1. The default empty string hostname ("") doesn't work and should be removed from the example.
  2. The port number matters in the hostname if using something other that the http and https defaults. With these two tidbits, I've gotten it to work now.
jaytxrx commented 1 year ago

@felicianotech can you please paste the working code here ?

FelicianoTech commented 1 year ago

@felicianotech can you please paste the working code here ?

Sure


So what I've attempted to do is taking my actual, working code for my private project, and simplified it to what should be needed for this example. I hope it helps.

package main

import (
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/hostrouter"
    log "github.com/sirupsen/logrus"
)

func main() {

    // create routers
    // coreRouter will be the top-level router that the hostrouter will be
    // mounted to.
    coreRouter = chi.NewRouter()
    // webappRouter is the main router for my app. The entire
    // webapp/backend system is powered using this router/these routes.
    webappRouter = chi.NewRouter()
    // publicRouter is used only for the wildcard subdomains to serve very
    // basic and simple pages.
    publicRouter = chi.NewRouter()

    webappRouter.Use(loggingMiddleware)
    webappRouter.Use(authMiddleware)
    // manages static files for the main webapp
    webappRouter.Get("/assets/*", func(w http.ResponseWriter, r *http.Request) {
        http.StripPrefix("/assets/", http.FileServer(http.Dir(a.ThemeRoot+"static"))).ServeHTTP(w, r)
    })
    // manages static files for the simply website on subdomains (uses different assets)
    publicRouter.Get("/assets/*", func(w http.ResponseWriter, r *http.Request) {
        http.StripPrefix("/assets/", http.FileServer(http.Dir(a.ThemeRoot+"public-static"))).ServeHTTP(w, r)
    })

    // here is where the host mapping to sub-routers take place
    // if you're using non-standard ports for http or https, the port
    // number needs to show up here
    hr := hostrouter.New()
    hr.Map("*.myapp.com:9000", publicRouter)
    // this matches anything not matched above
    hr.Map("*", webappRouter)

    // these are functions I have, in a different file, where I add the
    // routers that belong to each router/webapp
    addWebappRoutes()
    addPublicRoutes()

    // the hostrouter gets mounted to the core router here, completing the setup
    coreRouter.Mount("/", hr)

    // an HTTP server is run with the core router
    log.Fatal(http.ListenAndServe(":9000", coreRouter))
}
FelicianoTech commented 1 year ago

A little update I learned from my previous post. The port numbers are needed when you are using them directly in the browser/curl. This applies to a dev environment for example. When I got my app in production, since it was behind a proxy, I actually had to not use the port numbers for the prod environment in order to get it to work.

VojtechVitek commented 1 month ago

Hi, can someone please provide a PR with a fix to the example code, if this is still a valid issue?