HandleProxy handles forward HTTP proxy requests from DuckDB httpfs. Expects absolute-form URIs (scheme + host + path in the request-line).
(w http.ResponseWriter, r *http.Request)
| 254 | _ = upstream.Close() |
| 255 | _ = client.Close() |
| 256 | }() |
| 257 | go func() { |
| 258 | defer close(clientDone) |
| 259 | n, _ := io.Copy(client, upstream) |
| 260 | recvFromUpstream = n |
| 261 | _ = upstream.Close() |
| 262 | _ = client.Close() |
| 263 | }() |
| 264 | |
| 265 | go func() { |
| 266 | <-upstreamDone |
| 267 | <-clientDone |
| 268 | span.SetAttributes( |
| 269 | attribute.Int64("duckgres.connect.sent_bytes", sentToUpstream), |
| 270 | attribute.Int64("duckgres.connect.recv_bytes", recvFromUpstream), |
| 271 | ) |
| 272 | span.End() |
| 273 | slog.Info("Forward-proxy CONNECT closed.", |
| 274 | "target", target, |
| 275 | "sent_bytes", sentToUpstream, |
| 276 | "recv_bytes", recvFromUpstream, |
| 277 | "duration_ms", time.Since(connectStart).Milliseconds()) |
| 278 | }() |
| 279 | } |
| 280 | |
| 281 | // Hop-by-hop headers per RFC 7230 §6.1 — must not be forwarded. |
| 282 | var hopByHop = map[string]bool{ |
| 283 | "connection": true, |
| 284 | "keep-alive": true, |
| 285 | "proxy-authenticate": true, |
| 286 | "proxy-authorization": true, |
| 287 | "te": true, |
| 288 | "trailers": true, |
| 289 | "transfer-encoding": true, |
| 290 | "upgrade": true, |
| 291 | } |
| 292 | |
| 293 | // cachePassthroughHeader is added only by duckgres's worker-local router. |
| 294 | // It selects the proxy's observable-but-uncached request path and is stripped |
| 295 | // before the origin request so it cannot affect S3 or a SigV4 request. |
| 296 | const cachePassthroughHeader = "X-Duckgres-Cache-Passthrough" |
| 297 | |
| 298 | func isInternalPropagationHeader(header string) bool { |
| 299 | return strings.EqualFold(header, "traceparent") || strings.EqualFold(header, "tracestate") |
| 300 | } |
| 301 | |
| 302 | // HandleProxy handles forward HTTP proxy requests from DuckDB httpfs. |
| 303 | // Expects absolute-form URIs (scheme + host + path in the request-line). |
| 304 | func (p *CacheProxy) HandleProxy(w http.ResponseWriter, r *http.Request) { |
| 305 | inflightRequests.Inc() |
| 306 | defer inflightRequests.Dec() |
| 307 | r = r.WithContext(otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))) |
| 308 | |
| 309 | // HTTPS via CONNECT tunnel — we can't cache encrypted traffic, but we must |
| 310 | // still tunnel it so DuckDB can reach external HTTPS sources (e.g. |
| 311 | // read_parquet('https://datasets.clickhouse.com/...')) while |
| 312 | // http_proxy is set globally. |
| 313 | if r.Method == http.MethodConnect { |