| 24 | } |
| 25 | |
| 26 | func ProxyHTTP(w http.ResponseWriter, r *http.Request) { |
| 27 | cfg, err := agentproxy.ConfigFromHeaders(r.Header) |
| 28 | if err != nil { |
| 29 | http.Error(w, fmt.Sprintf("failed to parse agent proxy config: %s", err.Error()), http.StatusBadRequest) |
| 30 | return |
| 31 | } |
| 32 | |
| 33 | transport := NewTransport() |
| 34 | if cfg.ResponseHeaderTimeout > 0 { |
| 35 | transport.ResponseHeaderTimeout = cfg.ResponseHeaderTimeout |
| 36 | } |
| 37 | if cfg.DisableCompression { |
| 38 | transport.DisableCompression = true |
| 39 | } |
| 40 | |
| 41 | // Strip the {API_BASE}/proxy/http prefix while preserving URL escaping. |
| 42 | // |
| 43 | // NOTE: `r.URL.Path` is decoded. If we rewrite it without keeping `RawPath` |
| 44 | // in sync, Go may re-escape the path (e.g. turning "%5B" into "%255B"), |
| 45 | // which breaks urls with percent-encoded characters, like Next.js static chunk URLs. |
| 46 | prefix := agent.APIEndpointBase + agent.EndpointProxyHTTP |
| 47 | r.URL.Path = strings.TrimPrefix(r.URL.Path, prefix) |
| 48 | if r.URL.RawPath != "" { |
| 49 | if after, ok := strings.CutPrefix(r.URL.RawPath, prefix); ok { |
| 50 | r.URL.RawPath = after |
| 51 | } else { |
| 52 | // RawPath is no longer a valid encoding for Path; force Go to re-derive it. |
| 53 | r.URL.RawPath = "" |
| 54 | } |
| 55 | } |
| 56 | r.RequestURI = "" |
| 57 | |
| 58 | targetURL := *r.URL |
| 59 | targetURL.Scheme = cfg.Scheme |
| 60 | targetURL.Host = cfg.Host |
| 61 | targetURL.Path = "" |
| 62 | targetURL.RawPath = "" |
| 63 | targetURL.RawQuery = "" |
| 64 | |
| 65 | transport.TLSClientConfig, err = cfg.BuildTLSConfig(&targetURL) |
| 66 | if err != nil { |
| 67 | http.Error(w, fmt.Sprintf("failed to build TLS client config: %s", err.Error()), http.StatusInternalServerError) |
| 68 | return |
| 69 | } |
| 70 | |
| 71 | rp := reverseproxy.NewReverseProxy(cfg.Host, &targetURL, transport) |
| 72 | rp.ServeHTTP(w, r) |
| 73 | } |