Pular para o conteúdo

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.

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 })

Em cookbook/reverse-proxy, inicie cada upstream em um terminal separado:

Janela do terminal
go run ./upstream server1 :8081
Janela do terminal
go run ./upstream server2 :8082

Em um terceiro terminal, também em cookbook/reverse-proxy, inicie o proxy:

Janela do terminal
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”.

Janela do terminal
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:

Janela do terminal
go run . -grouped

Acesse 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/.

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)
}
}
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.