|
4 | 4 | "context" |
5 | 5 | "crypto/md5" |
6 | 6 | "encoding/hex" |
| 7 | + "encoding/json" |
7 | 8 | "errors" |
8 | 9 | "fmt" |
9 | 10 | "io" |
@@ -209,24 +210,157 @@ func (m *MirrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
209 | 210 | } |
210 | 211 |
|
211 | 212 | func (m *MirrorHandler) responseCache(rw http.ResponseWriter, r *http.Request, file string, info sss.FileInfo) { |
212 | | - m.setHuggingFaceHeaders(rw, r) |
| 213 | + if err := m.setHuggingFaceHeaders(rw, r); err != nil { |
| 214 | + m.errorResponse(rw, r, err) |
| 215 | + return |
| 216 | + } |
213 | 217 | if m.NoRedirect { |
214 | 218 | m.serveFromCache(rw, r, file, info) |
215 | 219 | } else { |
216 | 220 | m.redirect(rw, r, file, info) |
217 | 221 | } |
218 | 222 | } |
219 | 223 |
|
220 | | -func (m *MirrorHandler) setHuggingFaceHeaders(rw http.ResponseWriter, r *http.Request) { |
| 224 | +func (m *MirrorHandler) setHuggingFaceHeaders(rw http.ResponseWriter, r *http.Request) error { |
221 | 225 | // Special handling for huggingface.co to add X-Repo-Commit header with HF_ENDPOINT |
222 | | - if r.Host == "huggingface.co" { |
223 | | - sl := strings.SplitN(r.URL.Path, "/", 6) |
224 | | - if len(sl) == 6 && |
225 | | - sl[3] == "resolve" && |
226 | | - len(sl[4]) == 40 { |
227 | | - rw.Header().Set("X-Repo-Commit", sl[4]) |
| 226 | + if m.RemoteCache == nil { |
| 227 | + return nil |
| 228 | + } |
| 229 | + |
| 230 | + if r.Host != "huggingface.co" { |
| 231 | + return nil |
| 232 | + } |
| 233 | + |
| 234 | + rIndex := strings.Index(r.URL.Path, "/resolve/") |
| 235 | + if rIndex < 0 { |
| 236 | + return nil |
| 237 | + } |
| 238 | + |
| 239 | + repoRef := r.URL.Path[rIndex+9:] |
| 240 | + slashIndex := strings.Index(repoRef, "/") |
| 241 | + if slashIndex >= 0 { |
| 242 | + repoRef = repoRef[:slashIndex] |
| 243 | + } |
| 244 | + |
| 245 | + if len(repoRef) == 40 { |
| 246 | + rw.Header().Set("X-Repo-Commit", repoRef) |
| 247 | + return nil |
| 248 | + } |
| 249 | + |
| 250 | + repoName := r.URL.Path[1:rIndex] |
| 251 | + repoType := "models" |
| 252 | + if strings.HasPrefix(repoName, "datasets/") { |
| 253 | + repoType = "datasets" |
| 254 | + repoName = strings.TrimPrefix(repoName, "datasets/") |
| 255 | + } else if strings.HasPrefix(repoName, "spaces/") { |
| 256 | + repoType = "spaces" |
| 257 | + repoName = strings.TrimPrefix(repoName, "spaces/") |
| 258 | + } |
| 259 | + |
| 260 | + file := fmt.Sprintf("huggingface.co/api/%s/%s/revision/%s", repoType, repoName, repoRef) |
| 261 | + if m.Logger != nil { |
| 262 | + m.Logger.Println("HF Repo Info", file) |
| 263 | + } |
| 264 | + |
| 265 | + ctx := r.Context() |
| 266 | + doneCache, err := m.uniqueLock(ctx, file) |
| 267 | + if err != nil { |
| 268 | + return err |
| 269 | + } |
| 270 | + |
| 271 | + setFromCache := func() { |
| 272 | + fr, err := m.RemoteCache.Reader(ctx, file) |
| 273 | + if err != nil { |
| 274 | + if m.Logger != nil { |
| 275 | + m.Logger.Println("HF Repo Reader error", file, err) |
| 276 | + } |
| 277 | + return |
| 278 | + } |
| 279 | + defer fr.Close() |
| 280 | + |
| 281 | + var sha struct { |
| 282 | + Sha string `json:"sha"` |
| 283 | + } |
| 284 | + |
| 285 | + _ = json.NewDecoder(fr).Decode(&sha) |
| 286 | + if sha.Sha != "" { |
| 287 | + rw.Header().Set("X-Repo-Commit", sha.Sha) |
228 | 288 | } |
229 | 289 | } |
| 290 | + |
| 291 | + cacheInfo, err := m.RemoteCache.Stat(ctx, file) |
| 292 | + if err != nil { |
| 293 | + if errors.Is(err, context.Canceled) { |
| 294 | + doneCache() |
| 295 | + return err |
| 296 | + } |
| 297 | + if m.Logger != nil { |
| 298 | + m.Logger.Println("HF Cache Miss", file, err) |
| 299 | + } |
| 300 | + } else { |
| 301 | + if m.Logger != nil { |
| 302 | + m.Logger.Println("HF Cache Hit", file) |
| 303 | + } |
| 304 | + |
| 305 | + if m.CIDNClient == nil { |
| 306 | + sourceCtx, sourceCancel := context.WithTimeout(ctx, m.CheckSyncTimeout) |
| 307 | + sourceInfo, err := httpHead(sourceCtx, m.client(), r.URL.String()) |
| 308 | + if err != nil { |
| 309 | + sourceCancel() |
| 310 | + if m.Logger != nil { |
| 311 | + m.Logger.Println("HF Source Miss", file, err) |
| 312 | + } |
| 313 | + setFromCache() |
| 314 | + doneCache() |
| 315 | + return nil |
| 316 | + } |
| 317 | + sourceCancel() |
| 318 | + |
| 319 | + sourceSize := sourceInfo.Size() |
| 320 | + cacheSize := cacheInfo.Size() |
| 321 | + if cacheSize != 0 && (sourceSize <= 0 || sourceSize == cacheSize) { |
| 322 | + setFromCache() |
| 323 | + doneCache() |
| 324 | + return nil |
| 325 | + } |
| 326 | + |
| 327 | + if m.Logger != nil { |
| 328 | + m.Logger.Println("HF Source change", file, sourceSize, cacheSize) |
| 329 | + } |
| 330 | + } |
| 331 | + |
| 332 | + } |
| 333 | + |
| 334 | + errCh := make(chan error, 1) |
| 335 | + |
| 336 | + go func() { |
| 337 | + defer doneCache() |
| 338 | + url := "https://" + file |
| 339 | + errCh <- m.cacheFile(context.Background(), url, file) |
| 340 | + }() |
| 341 | + |
| 342 | + select { |
| 343 | + case <-ctx.Done(): |
| 344 | + return err |
| 345 | + case err := <-errCh: |
| 346 | + if err != nil { |
| 347 | + if cacheInfo != nil { |
| 348 | + if m.Logger != nil { |
| 349 | + m.Logger.Println("HF Recache error", file, err) |
| 350 | + } |
| 351 | + setFromCache() |
| 352 | + return nil |
| 353 | + } |
| 354 | + |
| 355 | + if errors.Is(err, ErrNotOK) { |
| 356 | + return nil |
| 357 | + } |
| 358 | + return err |
| 359 | + } |
| 360 | + setFromCache() |
| 361 | + } |
| 362 | + |
| 363 | + return nil |
230 | 364 | } |
231 | 365 |
|
232 | 366 | func (m *MirrorHandler) setHeaders(rw http.ResponseWriter, info sss.FileInfo) { |
@@ -335,26 +469,33 @@ func (m *MirrorHandler) serveFromCache(rw http.ResponseWriter, r *http.Request, |
335 | 469 | } |
336 | 470 | } |
337 | 471 |
|
338 | | -func (m *MirrorHandler) cacheResponse(w http.ResponseWriter, r *http.Request) { |
339 | | - ctx := r.Context() |
340 | | - file := path.Join(r.Host, r.URL.EscapedPath()) |
341 | | - |
| 472 | +func (m *MirrorHandler) uniqueLock(ctx context.Context, file string) (func(), error) { |
342 | 473 | closeValue, loaded := m.mut.LoadOrStore(file, make(chan struct{})) |
343 | 474 | closeCh := closeValue.(chan struct{}) |
344 | 475 | for loaded { |
345 | 476 | select { |
346 | 477 | case <-ctx.Done(): |
347 | | - m.errorResponse(w, r, ctx.Err()) |
348 | | - return |
| 478 | + return nil, ctx.Err() |
349 | 479 | case <-closeCh: |
350 | 480 | } |
351 | 481 | closeValue, loaded = m.mut.LoadOrStore(file, make(chan struct{})) |
352 | 482 | closeCh = closeValue.(chan struct{}) |
353 | 483 | } |
354 | 484 |
|
355 | | - doneCache := func() { |
| 485 | + return func() { |
356 | 486 | m.mut.Delete(file) |
357 | 487 | close(closeCh) |
| 488 | + }, nil |
| 489 | +} |
| 490 | + |
| 491 | +func (m *MirrorHandler) cacheResponse(w http.ResponseWriter, r *http.Request) { |
| 492 | + ctx := r.Context() |
| 493 | + file := path.Join(r.Host, r.URL.EscapedPath()) |
| 494 | + |
| 495 | + doneCache, err := m.uniqueLock(ctx, file) |
| 496 | + if err != nil { |
| 497 | + m.errorResponse(w, r, err) |
| 498 | + return |
358 | 499 | } |
359 | 500 |
|
360 | 501 | cacheInfo, err := m.RemoteCache.Stat(ctx, file) |
@@ -410,13 +551,7 @@ func (m *MirrorHandler) cacheResponse(w http.ResponseWriter, r *http.Request) { |
410 | 551 |
|
411 | 552 | go func() { |
412 | 553 | defer doneCache() |
413 | | - var err error |
414 | | - if m.CIDNClient != nil { |
415 | | - err = m.cacheFileWithCIDN(context.Background(), r.URL.String(), file) |
416 | | - } else { |
417 | | - err = m.cacheFile(context.Background(), r.URL.String(), file) |
418 | | - } |
419 | | - errCh <- err |
| 554 | + errCh <- m.cacheFile(context.Background(), r.URL.String(), file) |
420 | 555 | }() |
421 | 556 |
|
422 | 557 | select { |
@@ -465,6 +600,13 @@ func formatGroup(s string) string { |
465 | 600 | return u.String() |
466 | 601 | } |
467 | 602 |
|
| 603 | +func (m *MirrorHandler) cacheFile(ctx context.Context, sourceFile, cacheFile string) error { |
| 604 | + if m.CIDNClient != nil { |
| 605 | + return m.cacheFileWithCIDN(context.Background(), sourceFile, cacheFile) |
| 606 | + } |
| 607 | + return m.cacheFileDirect(context.Background(), sourceFile, cacheFile) |
| 608 | +} |
| 609 | + |
468 | 610 | func (m *MirrorHandler) cacheFileWithCIDN(ctx context.Context, sourceFile, cacheFile string) error { |
469 | 611 | blobs := m.CIDNClient.TaskV1alpha1().Blobs() |
470 | 612 | name := getBlobName(cacheFile) |
@@ -601,7 +743,7 @@ func (m *MirrorHandler) cacheFileWithCIDN(ctx context.Context, sourceFile, cache |
601 | 743 | } |
602 | 744 | } |
603 | 745 |
|
604 | | -func (m *MirrorHandler) cacheFile(ctx context.Context, sourceFile, cacheFile string) error { |
| 746 | +func (m *MirrorHandler) cacheFileDirect(ctx context.Context, sourceFile, cacheFile string) error { |
605 | 747 | resp, info, err := httpGet(ctx, m.client(), sourceFile) |
606 | 748 | if err != nil { |
607 | 749 | return err |
|
0 commit comments