102 lines
2.6 KiB
Go
102 lines
2.6 KiB
Go
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
var localhostRegex *regexp.Regexp = regexp.MustCompile(`^(localhost|127\.0\.0\.1|\d+\.\d+\.\d+\.\d+)`)
|
|
|
|
var proxyRequestsTotal = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "silverbullet_proxy_requests",
|
|
Help: "Total number of proxy requests in the SilverBullet space",
|
|
})
|
|
|
|
func init() {
|
|
prometheus.MustRegister(proxyRequestsTotal)
|
|
}
|
|
|
|
func proxyHandler(w http.ResponseWriter, r *http.Request) {
|
|
spaceConfig := spaceConfigFromContext(r.Context())
|
|
if spaceConfig.ReadOnlyMode {
|
|
http.Error(w, "Read only mode, no proxy allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
// Extract the URI from the path
|
|
path := chi.URLParam(r, "*")
|
|
if path == "" {
|
|
http.Error(w, "No URI provided", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Add query parameters if they exist
|
|
if r.URL.RawQuery != "" {
|
|
path += "?" + r.URL.RawQuery
|
|
}
|
|
|
|
// Determine protocol based on hostname
|
|
var targetURL string
|
|
if localhostRegex.MatchString(path) {
|
|
targetURL = "http://" + path
|
|
} else {
|
|
targetURL = "https://" + path
|
|
}
|
|
|
|
log.Printf("Proxying to %s\n", targetURL)
|
|
|
|
// Create the proxy request
|
|
req, err := http.NewRequest(r.Method, targetURL, r.Body)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Copy headers starting with X-Proxy-Header-, removing the prefix
|
|
for key, values := range r.Header {
|
|
if strings.HasPrefix(strings.ToLower(key), "x-proxy-header-") {
|
|
headerName := key[len("x-proxy-header-"):]
|
|
for _, value := range values {
|
|
req.Header.Set(headerName, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Replaced with specifically configured client
|
|
client := http.DefaultClient
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
log.Printf("Error fetching proxied URL: %v\n", err)
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Copy response response header with x-proxy-header prefix to keep things clean
|
|
for key, values := range resp.Header {
|
|
for _, value := range values {
|
|
w.Header().Set(fmt.Sprintf("x-proxy-header-%s", key), value)
|
|
}
|
|
}
|
|
|
|
// Put status code in header
|
|
w.Header().Set("x-proxy-status-code", strconv.Itoa(resp.StatusCode))
|
|
w.Header().Set("content-type", resp.Header.Get("content-type"))
|
|
// Always return 200, to avoid interference, actual status code is in x-proxy-status-code
|
|
w.WriteHeader(http.StatusOK)
|
|
// Copy body
|
|
if _, err := io.Copy(w, resp.Body); err != nil {
|
|
log.Printf("Proxy: failed to copy response body: %v", err)
|
|
}
|
|
proxyRequestsTotal.Inc()
|
|
}
|