122 lines
2.8 KiB
Go
122 lines
2.8 KiB
Go
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/pocketbase/pocketbase"
|
|
"github.com/pocketbase/pocketbase/core"
|
|
"github.com/pocketbase/pocketbase/tools/router"
|
|
)
|
|
|
|
var Logger *slog.Logger
|
|
|
|
func ServeProxyPassingToNuxt(app *pocketbase.PocketBase, se *core.ServeEvent) *router.Route[*core.RequestEvent] {
|
|
Logger = app.Logger()
|
|
|
|
proxyUrl, _ := app.RootCmd.Flags().GetString("proxy")
|
|
|
|
if len(proxyUrl) > 0 {
|
|
target, _ := url.Parse(proxyUrl) // Node.js app
|
|
|
|
proxy := httputil.NewSingleHostReverseProxy(target)
|
|
|
|
originalDirector := proxy.Director
|
|
proxy.Director = func(req *http.Request) {
|
|
originalDirector(req)
|
|
req.Host = target.Host
|
|
}
|
|
proxy.Transport = &loggingTransport{http.DefaultTransport}
|
|
|
|
proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {
|
|
if errors.Is(err, context.Canceled) {
|
|
return
|
|
}
|
|
app.Logger().Error(fmt.Sprintf("Proxy error: %v for %s %s", err, req.Method, req.URL.Path), "type", "proxy")
|
|
http.Error(rw, "Proxy error", http.StatusBadGateway)
|
|
}
|
|
|
|
return se.Router.Any("/", func(e *core.RequestEvent) error {
|
|
// Ping the backend with a HEAD request (or TCP dial)
|
|
backendUp := isBackendAlive(target)
|
|
if !backendUp {
|
|
app.Logger().Error(fmt.Sprintf("Backend %s is unreachable, sending 502", target), "type", "proxy", "path", e.Request.URL.Path, "remoteIP", e.Request.RemoteAddr)
|
|
e.Response.WriteHeader(http.StatusBadGateway)
|
|
e.Response.Write([]byte("502 Backend is unavailable"))
|
|
return nil
|
|
}
|
|
|
|
// Forward to Node.js
|
|
proxy.ServeHTTP(e.Response, e.Request)
|
|
return nil
|
|
})
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func isBackendAlive(target *url.URL) bool {
|
|
conn, err := net.DialTimeout("tcp", target.Host, 500*time.Millisecond)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
conn.Close()
|
|
return true
|
|
}
|
|
|
|
type loggingTransport struct {
|
|
rt http.RoundTripper
|
|
}
|
|
|
|
func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
start := time.Now()
|
|
|
|
// Do the actual request
|
|
resp, err := t.rt.RoundTrip(req)
|
|
duration := time.Since(start)
|
|
|
|
// Prepare fields
|
|
remoteAddr := req.RemoteAddr
|
|
if ip := req.Header.Get("X-Real-IP"); len(ip) > 0 {
|
|
remoteAddr = ip
|
|
}
|
|
method := req.Method
|
|
uri := req.URL.RequestURI()
|
|
// proto := req.Proto
|
|
status := 0
|
|
size := 0
|
|
|
|
if resp != nil {
|
|
status = resp.StatusCode
|
|
if resp.ContentLength > 0 {
|
|
size = int(resp.ContentLength)
|
|
}
|
|
}
|
|
|
|
referer := req.Referer()
|
|
ua := req.UserAgent()
|
|
// timestamp := time.Now().Format("02/Jan/2006:15:04:05 -0700")
|
|
|
|
Logger.Info(
|
|
fmt.Sprintf("%s %s", method, uri),
|
|
"type", "proxy",
|
|
// "method", method,
|
|
// "url", uri,
|
|
"referer", referer,
|
|
"remoteIP", remoteAddr,
|
|
"userAgent", ua,
|
|
"execTime", fmt.Sprintf("%.4fms", float64(duration.Milliseconds())),
|
|
"status", status,
|
|
"size", size,
|
|
)
|
|
|
|
return resp, err
|
|
}
|