Reverse Proxy
Esta receita demonstra como usar Echo como reverse proxy e load balancer na frente das suas aplicações, como WordPress, Node.js, Java, Python, Ruby ou Go. Por simplicidade, os upstreams aqui são servidores Go que também tratam WebSocket.
1) Identificar URL(s) de destino upstream
Seção intitulada “1) Identificar URL(s) de destino upstream”url1, err := url.Parse("http://localhost:8081")if err != nil { e.Logger.Error("failed to parse url", "error", err) return}url2, err := url.Parse("http://localhost:8082")if err != nil { e.Logger.Error("failed to parse url", "error", err) return}targets := []*middleware.ProxyTarget{ {URL: url1}, {URL: url2},}2) Configurar middleware de proxy com destinos upstream
Seção intitulada “2) Configurar middleware de proxy com destinos upstream”O snippet abaixo usa load balancing round-robin. Você também pode usar
middleware.NewRandomBalancer().
e.Use(middleware.Proxy(middleware.NewRoundRobinBalancer(targets)))Para configurar um proxy para uma sub-rota, use Echo#Group().
// Canonicalize the group root so relative WebSocket URLs stay under /blog/.e.Any("/blog", func(c *echo.Context) error { location := "/blog/" if query := c.Request().URL.RawQuery; query != "" { location += "?" + query } return c.Redirect(http.StatusPermanentRedirect, location)})g := e.Group("/blog")g.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{ Balancer: middleware.NewRoundRobinBalancer(targets), Rewrite: map[string]string{"/blog/*": "/$1"},}))// Group middleware needs a matching route. The proxy handles the response.g.Any("/*", func(c *echo.Context) error { return echo.ErrNotFound })3) Iniciar servidores upstream
Seção intitulada “3) Iniciar servidores upstream”Em cookbook/reverse-proxy, inicie cada upstream em um terminal separado:
go run ./upstream server1 :8081go run ./upstream server2 :80824) Iniciar o servidor proxy
Seção intitulada “4) Iniciar o servidor proxy”Em um terceiro terminal, também em cookbook/reverse-proxy, inicie o proxy:
go run .Acesse http://localhost:1323, e você deverá ver uma página com um request HTTP
servido pelo “server 1” e um request WebSocket servido pelo “server 2”.
HTTP
Hello from upstream server server1
WebSocket
Hello from upstream server server2!Hello from upstream server server2!Hello from upstream server server2!Para usar o modo de grupo, pare o proxy padrão e execute:
go run . -groupedAcesse http://localhost:1323/blog/. O proxy remove /blog antes de encaminhar as requisições; a URL relativa de WebSocket da página permanece em /blog/.
Código-fonte
Seção intitulada “Código-fonte”Servidor upstream
Seção intitulada “Servidor upstream”package main
import ( "context" "fmt" "net/http" "os" "time"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5/middleware" "golang.org/x/net/websocket")
var index = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Upstream Server</title> <style> h1, p { font-weight: 300; } </style> </head> <body> <h1>HTTP</h1> <p> Hello from upstream server %s </p> <h1>WebSocket</h1> <p id="output"></p> <script> var wsURL = new URL('ws', window.location.href) wsURL.protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' var ws = new WebSocket(wsURL)
ws.onmessage = function(evt) { var out = document.getElementById('output'); out.innerHTML += evt.data + '<br>'; } </script> </body> </html>`
func main() { name := os.Args[1] port := os.Args[2] e := echo.New()
e.Use(middleware.RequestLogger()) e.Use(middleware.Recover())
e.GET("/", func(c *echo.Context) error { return c.HTML(http.StatusOK, fmt.Sprintf(index, name)) })
// WebSocket handler e.GET("/ws", func(c *echo.Context) error { websocket.Handler(func(ws *websocket.Conn) { defer ws.Close() for { // Write err := websocket.Message.Send(ws, fmt.Sprintf("Hello from upstream server %s!", name)) if err != nil { e.Logger.Error("failed to send message", "error", err) } select { case <-ws.Request().Context().Done(): return case <-time.After(1 * time.Second): continue } } }).ServeHTTP(c.Response(), c.Request()) return nil })
sc := echo.StartConfig{Address: port} if err := sc.Start(context.Background(), e); err != nil { e.Logger.Error("failed to start server", "error", err) }}Servidor proxy
Seção intitulada “Servidor proxy”package main
import ( "context" "flag" "net/http" "net/url"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5/middleware")
func main() { grouped := flag.Bool("grouped", false, "limit the proxy to the /blog route group") flag.Parse() e := echo.New() e.Use(middleware.RequestLogger()) e.Use(middleware.Recover())
// Setup proxy url1, err := url.Parse("http://localhost:8081") if err != nil { e.Logger.Error("failed to parse url", "error", err) return } url2, err := url.Parse("http://localhost:8082") if err != nil { e.Logger.Error("failed to parse url", "error", err) return } targets := []*middleware.ProxyTarget{ {URL: url1}, {URL: url2}, } if *grouped { configureBlogProxy(e, targets) } else { e.Use(middleware.Proxy(middleware.NewRoundRobinBalancer(targets))) }
sc := echo.StartConfig{Address: ":1323"} if err := sc.Start(context.Background(), e); err != nil { e.Logger.Error("failed to start server", "error", err) }}
// configureBlogProxy limits the proxy middleware to a route group.func configureBlogProxy(e *echo.Echo, targets []*middleware.ProxyTarget) { // Canonicalize the group root so relative WebSocket URLs stay under /blog/. e.Any("/blog", func(c *echo.Context) error { location := "/blog/" if query := c.Request().URL.RawQuery; query != "" { location += "?" + query } return c.Redirect(http.StatusPermanentRedirect, location) }) g := e.Group("/blog") g.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{ Balancer: middleware.NewRoundRobinBalancer(targets), Rewrite: map[string]string{"/blog/*": "/$1"}, })) // Group middleware needs a matching route. The proxy handles the response. g.Any("/*", func(c *echo.Context) error { return echo.ErrNotFound })}Atualização de segurança do Echo (v5.4.0 / v4.16.0)
Seção intitulada “Atualização de segurança do Echo (v5.4.0 / v4.16.0)”Proxy define X-Forwarded-Proto a partir de Context#Scheme() e remove os outros cabeçalhos de esquema. No v5, X-Real-IP vem de Context#RealIP(). Se houver outro proxy antes do Echo, configure os extratores de esquema e IP para transmitir valores corretos ao upstream.