initial commit
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ProxyConfig holds options for the reverse proxy.
|
||||
type ProxyConfig struct {
|
||||
TargetURL *url.URL
|
||||
PreserveHost bool
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
// loggingResponseWriter wraps http.ResponseWriter to capture the HTTP status code.
|
||||
type loggingResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (lrw *loggingResponseWriter) WriteHeader(code int) {
|
||||
lrw.statusCode = code
|
||||
lrw.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// NewProxyHandler creates an http.Handler that reverse proxies requests to the target URL.
|
||||
func NewProxyHandler(cfg ProxyConfig) http.Handler {
|
||||
proxy := httputil.NewSingleHostReverseProxy(cfg.TargetURL)
|
||||
|
||||
// Transport configured with InsecureSkipVerify to trust all backend certificates
|
||||
proxy.Transport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS10,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
},
|
||||
}
|
||||
|
||||
originalDirector := proxy.Director
|
||||
proxy.Director = func(req *http.Request) {
|
||||
originalDirector(req)
|
||||
|
||||
// Set Host header to target host unless preserve host is explicitly requested.
|
||||
// This is vital for backend virtual hosting and SNI.
|
||||
if !cfg.PreserveHost {
|
||||
req.Host = cfg.TargetURL.Host
|
||||
}
|
||||
|
||||
if clientProto := req.Header.Get("X-Forwarded-Proto"); clientProto == "" {
|
||||
if req.TLS != nil {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
} else {
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
log.Printf("[ERROR] %s %s -> Backend error: %v", r.Method, r.URL.RequestURI(), err)
|
||||
http.Error(w, fmt.Sprintf("Bad Gateway (smallprox): %v", err), http.StatusBadGateway)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
lrw := &loggingResponseWriter{ResponseWriter: w, statusCode: http.StatusOK}
|
||||
|
||||
proxy.ServeHTTP(lrw, r)
|
||||
|
||||
duration := time.Since(start)
|
||||
tlsVer := "HTTP"
|
||||
if r.TLS != nil {
|
||||
tlsVer = tlsVersionName(r.TLS.Version)
|
||||
}
|
||||
|
||||
log.Printf("[%s] %s | %s %s -> %d (%s)",
|
||||
tlsVer,
|
||||
r.RemoteAddr,
|
||||
r.Method,
|
||||
r.URL.RequestURI(),
|
||||
lrw.statusCode,
|
||||
duration.Round(time.Millisecond/10),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// ParseTargetURL cleans and parses the backend target URL string.
|
||||
func ParseTargetURL(raw string) (*url.URL, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return nil, fmt.Errorf("backend URL cannot be empty")
|
||||
}
|
||||
|
||||
if !strings.Contains(raw, "://") {
|
||||
raw = "https://" + raw
|
||||
}
|
||||
|
||||
parsed, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid backend URL %q: %w", raw, err)
|
||||
}
|
||||
|
||||
if parsed.Scheme != "http" && parsed.Scheme != "https" {
|
||||
return nil, fmt.Errorf("unsupported backend scheme %q (must be http or https)", parsed.Scheme)
|
||||
}
|
||||
|
||||
if parsed.Host == "" {
|
||||
return nil, fmt.Errorf("backend URL must have a host")
|
||||
}
|
||||
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// tlsVersionName returns the human-readable string for a TLS version identifier.
|
||||
func tlsVersionName(ver uint16) string {
|
||||
switch ver {
|
||||
case tls.VersionTLS10:
|
||||
return "TLS 1.0"
|
||||
case tls.VersionTLS11:
|
||||
return "TLS 1.1"
|
||||
case tls.VersionTLS12:
|
||||
return "TLS 1.2"
|
||||
case tls.VersionTLS13:
|
||||
return "TLS 1.3"
|
||||
default:
|
||||
return fmt.Sprintf("TLS 0x%04x", ver)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user