Ir al contenido

Reverse Proxy

Esta receta demuestra cómo usar Echo como reverse proxy y load balancer delante de tus aplicaciones, como WordPress, Node.js, Java, Python, Ruby o Go. Para simplificar, aquí los upstreams son servidores Go que también manejan 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 proxy con destinos upstream

Sección titulada «2) Configurar middleware proxy con destinos upstream»

El snippet de abajo usa load balancing round-robin. También puedes usar middleware.NewRandomBalancer().

e.Use(middleware.Proxy(middleware.NewRoundRobinBalancer(targets)))

Para configurar un proxy para una subruta, usa 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 })

Desde cookbook/reverse-proxy, inicia cada upstream en su propia terminal:

Ventana de terminal
go run ./upstream server1 :8081
Ventana de terminal
go run ./upstream server2 :8082

En una tercera terminal, también en cookbook/reverse-proxy, inicia el proxy:

Ventana de terminal
go run .

Abre http://localhost:1323, y deberías ver una página web con un request HTTP servido desde “server 1” y un request WebSocket servido desde “server 2”.

Ventana de 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 el modo de grupo, detén el proxy predeterminado y ejecuta:

Ventana de terminal
go run . -grouped

Abre http://localhost:1323/blog/. El proxy elimina /blog antes de reenviar las solicitudes; la URL relativa de WebSocket de la página permanece bajo /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 })
}

Actualización de seguridad de Echo (v5.4.0 / v4.16.0)

Sección titulada «Actualización de seguridad de Echo (v5.4.0 / v4.16.0)»

Proxy reenvía X-Forwarded-Proto desde Context#Scheme() y elimina las otras cabeceras de esquema. En v5, X-Real-IP procede de Context#RealIP(). Si hay otro proxy delante de Echo, configura los extractores de esquema e IP para que el upstream reciba valores correctos.