handleConnect tunnels an HTTPS CONNECT request. We don't cache — just copy bytes between client and origin. Required because SET GLOBAL http_proxy makes DuckDB tunnel all HTTPS through us, including external sources. What we CAN log: tunnel open (Info), dial / hijack errors (Warn / Error), final by
(w http.ResponseWriter, r *http.Request)
| 158 | |
| 159 | func NewCacheProxy(store *DiskCache, peers *PeerManager, cacheHostSuffixes []string) *CacheProxy { |
| 160 | return &CacheProxy{ |
| 161 | store: store, |
| 162 | peers: peers, |
| 163 | client: &http.Client{Timeout: defaultOriginTimeout}, |
| 164 | connectDial: net.DialTimeout, |
| 165 | originTimeout: defaultOriginTimeout, |
| 166 | originRetryMaxAttempts: defaultOriginRetryMaxAttempts, |
| 167 | originRetryInitialBackoff: defaultOriginRetryInitialBackoff, |
| 168 | originRetryMaxBackoff: defaultOriginRetryMaxBackoff, |
| 169 | cacheHostSuffixes: cacheHostSuffixes, |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | // How long a peer's /cache/get (or a local wait on a peer's in-flight fill) |
| 174 | // may block waiting for that peer's fill to land before giving up to origin. |
| 175 | // Long enough for a healthy multi-block fill, short enough that a wedged peer |
| 176 | // can't hold request latency hostage once the origin would have answered. |
| 177 | const peerFillWait = 10 * time.Second |
| 178 | |
| 179 | // shouldCache returns true if the request targets a host we want to cache. |
| 180 | // When no suffixes are configured, all GETs are cached (legacy behavior). |
| 181 | func (p *CacheProxy) shouldCache(r *http.Request) bool { |
| 182 | if len(p.cacheHostSuffixes) == 0 { |
| 183 | return true |
| 184 | } |
| 185 | host := r.URL.Host |
| 186 | if host == "" { |
| 187 | host = r.Host |
| 188 | } |
| 189 | for _, s := range p.cacheHostSuffixes { |
| 190 | if strings.Contains(host, s) { |
| 191 | return true |
| 192 | } |
| 193 | } |
| 194 | return false |
| 195 | } |
| 196 | |
| 197 | // handleConnect tunnels an HTTPS CONNECT request. We don't cache — just copy |
| 198 | // bytes between client and origin. Required because SET GLOBAL http_proxy |
| 199 | // makes DuckDB tunnel all HTTPS through us, including external sources. |
| 200 | // |
| 201 | // What we CAN log: tunnel open (Info), dial / hijack errors (Warn / Error), |
| 202 | // final byte counts and duration on close (Info). What we CANNOT log: the |
| 203 | // actual HTTP request / response inside the tunnel — TLS terminates between |
| 204 | // the worker and the origin, so the encrypted bytes flowing past us are |
| 205 | // opaque. An S3 501 with an XML error envelope going through CONNECT is |
| 206 | // invisible to us at the body level; only "CONNECT to s3:443 → N bytes |
| 207 | // in / M bytes out / closed in T ms" is recoverable. |
| 208 | // |
| 209 | // We log this anyway because a black hole is worse than a partial trail — |
| 210 | // at least we can confirm a request was attempted, see the target host, |
| 211 | // and spot dial failures. For full request/response visibility on writes, |
| 212 | // DuckDB has to actually use plain HTTP via forwardUncached (s3_use_ssl = |
| 213 | // false), which httpfs has been observed ignoring for some PUT paths. |
| 214 | // |
| 215 | // The proxy binds a hostPort and is reachable cluster-wide without |
| 216 | // authentication, so the tunnel target is restricted (connectRefusalReason): |
| 217 | // port 443 only, no local IP literals, optionally a hostname suffix list. |