ipn/ipnlocal: implement the reverse proxy HTTP handler type
Updates tailscale/corp#7515
"io"
"net"
"net/http"
+ "net/http/httputil"
"net/netip"
+ "net/url"
pathpkg "path"
+ "strings"
"time"
"tailscale.com/ipn"
@@ -156,13 +159,52 @@ func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) {
return
}
if v := h.Proxy(); v != "" {
- io.WriteString(w, "TODO(bradfitz): proxy")
+ // TODO(bradfitz): this is a lot of setup per HTTP request. We should
+ // build the whole http.Handler with all the muxing and child handlers
+ // only on start/config change. But this works for now (2022-11-09).
+ u, err := url.Parse(expandProxyArg(v))
+ if err != nil {
+ http.Error(w, "bad proxy config", http.StatusInternalServerError)
+ return
+ }
+ rp := httputil.NewSingleHostReverseProxy(u)
+ rp.Transport = &http.Transport{
+ DialContext: b.dialer.SystemDial,
+ }
+ rp.ServeHTTP(w, r)
return
}
http.Error(w, "empty handler", 500)
}
+// expandProxyArg returns a URL from s, where s can be of form:
+//
+// * port number ("8080")
+// * host:port ("localhost:8080")
+// * full URL ("http://localhost:8080", in which case it's returned unchanged)
+func expandProxyArg(s string) string {
+ if s == "" {
+ return ""
+ }
+ if strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") {
+ return s
+ }
+ if allNumeric(s) {
+ return "http://127.0.0.1:" + s
+ }
+ return "http://" + s
+}
+
+func allNumeric(s string) bool {
+ for i := 0; i < len(s); i++ {
+ if s[i] < '0' || s[i] > '9' {
+ return false
+ }
+ }
+ return s != ""
+}
+
func (b *LocalBackend) webServerConfig(sniName string, port uint16) (c ipn.WebServerConfigView, ok bool) {
key := ipn.HostPort(fmt.Sprintf("%s:%v", sniName, port))
