Help with Reverse Proxying a subpage/bundle reload


#1

Labstackers! I really love the echo framework (BIG THX :kissing_heart:) but I’m struggling with some recipes in my configuration!

First my setup:
setup

(only for those interested, I’ve deployed echo on localhost:80 combining cookbook subdomains + reverse proxying from/pointing to nodejs backend bundle on same localhost:8080. works fine, just like virtual hosts)

My goal is:

User requests hostname in my LAN (e.g. ‘hxxp://subpage’ > defined route in my gateway router, pointing to the IP of the device handling the servers). Echo should listen on 80, if hostname matches, it shall reverse proxy request properly from localhost:8080 (e.g. ‘hxxp://localhost:8080/subpage’).

Problem is:

Here is an example on how to create virtual hosts and proxy them one by one (missing some parts but you get the point):

// Host …
type Host struct {
Echo *echo.Echo
}

func main() {
	// Hosts
	hosts := map[string]*Host{}
	
	//-------
	// subpage
	//-------

	subpage := echo.New()
	subpage.Use(middleware.Logger())
	subpage.Use(middleware.Recover())

	hosts["subpage"] = &Host{subpage}

	// Reverse Proxy
	spURL, err := url.Parse("hxxp://localhost:8080")

	if err != nil {
		subpage.Logger.Fatal(err)
	}

	spTarget := []*middleware.ProxyTarget{
		{
			URL: spURL,
		},
	}

	subpage.POST("/do", subpageHandler)

	subpage.Group("", middleware.Proxy(middleware.NewRoundRobinBalancer(spTarget)))

	e := echo.New()

	e.Debug = true

	e.Any("/*", func(c echo.Context) (err error) {
		req := c.Request()
		res := c.Response()
		host := hosts[req.Host]

		if host == nil {
			err = echo.ErrNotFound
		} else {
			host.Echo.ServeHTTP(res, req)
		}

		return
	})

	e.Logger.Fatal(e.Start(":80"))
}

(POST request from my backend server is handled very well by my echo server, thx to Cookbookers/File Upload Recipe)

My subpage is deployed as own app in my vue cli webpack devserver bundle.

This doesn’t work:
spURL, err := url.Parse(“hxxp://localhost:8080/subpage”)

If I provide localhost:8080 here (without the path), GET works. Echo may be unable to parse reloaded response body (only header) leading to error and ‘blank’ html page:

Uncaught SyntaxError: Unexpected token < subpage.js:1

It is the correct subpage.html but it doesn’t show it’s content. If I GET request localhost:8080/subpage directly in my browser, it works. Only Echo not able to load everything :frowning:

I’m fairly new to all of this but I think this is possible with Echo. Do I have to use streaming response recipe and flush the echo context response?:thinking:

Thanks in advance, keep coding guys :wink::blush:


#2

It did not went into details but have you tried https://armor.labstack.com? It is much easier to domain + proxy using a yaml file. If you are not using any login in Echo go for Armor. I can help you with the setup.


#3

No need to vishr. I reject using armor in my local project because I want to stick to echo and golang for some simple dev apps only. But I know it’s very good and maybe I’ll use it in a web project later on!

And after four weeks and almost 85 work hours struggling, I finally managed to realized what I planned to do with echo and golang only. :heart_eyes:

You didn’t even know vishr but your answer in this post:

is the final solution. The backend proxy expecting a request path like localhost/subpage to handle the request/serve the content correctly. So I it’s all about rewriting the url before it’s handled with a middleware:

// Host …
type Host struct {
Echo *echo.Echo
}

func main() {

	// Hosts
	hosts := map[string]*Host{}

	//-------
	// Subpage
	//-------

	subpage := echo.New()
	// custom middleware to change request url path
	subpage.Pre(func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			if c.Request().URL.Path == "/" {
				c.Request().URL.Path = "/subpage"
			}
			return next(c)
		}
	})
	subpage.Use(middleware.Logger())
	subpage.Use(middleware.Recover())

	hosts["subpage"] = &Host{subpage}

	// Reverse Proxy
	subpageURL, err := url.Parse("http://localhost:8080")

	if err != nil {
		subpage.Logger.Fatal(err)
	}

	subpageTarget := []*middleware.ProxyTarget{
		{
			URL: subpageURL,
		},
	}

	subpage.POST("/do", subpageHandler)

	subpage.Group("", middleware.Proxy(middleware.NewRoundRobinBalancer(subpageTarget)))

	e := echo.New()

	e.Debug = true

	e.Any("/*", func(c echo.Context) (err error) {
		req := c.Request()
		res := c.Response()
		host := hosts[req.Host]

		if host == nil {
			err = echo.ErrNotFound
		} else {
			host.Echo.ServeHTTP(res, req)
		}

		return
	})

	e.Logger.Fatal(e.Start(":80"))
}

Works like a charm :hearts: