@@ -133,16 +133,37 @@ type HFClient struct {
133133 BaseURL string
134134 APIKey string
135135 HTTPClient * http.Client
136+ // downloadClient streams file bodies. Unlike HTTPClient it has no overall
137+ // Timeout, because http.Client.Timeout also interrupts Response.Body reads —
138+ // which would kill large/slow downloads handed to handlers.HandleFile.
139+ downloadClient * http.Client
136140}
137141
138142// NewHFClient creates a new HF client
139143func NewHFClient (baseURL , apiKey string , timeout time.Duration ) * HFClient {
144+ // Bound the connect + response-header phase (including redirect hops to the
145+ // CDN) without capping the body read; overall cancellation comes from the
146+ // request context. Built explicitly (rather than cloning DefaultTransport)
147+ // so it stays a *http.Transport even when tests swap the default transport.
148+ downloadTransport := & http.Transport {
149+ Proxy : http .ProxyFromEnvironment ,
150+ ForceAttemptHTTP2 : true ,
151+ MaxIdleConns : 100 ,
152+ IdleConnTimeout : 90 * time .Second ,
153+ TLSHandshakeTimeout : 10 * time .Second ,
154+ ExpectContinueTimeout : 1 * time .Second ,
155+ ResponseHeaderTimeout : timeout ,
156+ }
157+
140158 return & HFClient {
141159 BaseURL : baseURL ,
142160 APIKey : apiKey ,
143161 HTTPClient : & http.Client {
144162 Timeout : timeout ,
145163 },
164+ downloadClient : & http.Client {
165+ Transport : downloadTransport ,
166+ },
146167 }
147168}
148169
@@ -278,7 +299,7 @@ func (c *HFClient) DownloadBucketFile(ctx context.Context, bucketID string, path
278299 }
279300 req .Header .Set ("Authorization" , "Bearer " + c .APIKey )
280301
281- resp , err := c .HTTPClient .Do (req )
302+ resp , err := c .downloadClient .Do (req )
282303 if err != nil {
283304 return nil , fmt .Errorf ("failed to download bucket file %s: %w" , path , err )
284305 }
0 commit comments