From f9e142dd51befff85f5d7e9d76da2446df8dbd08 Mon Sep 17 00:00:00 2001 From: Catherine Date: Tue, 11 Nov 2025 05:59:08 +0000 Subject: [PATCH] Observe all storage errors reported by GetManifest. Otherwise users may get jumpscares of "site not found" due to temporary conditions (network errors to S3 backend included). --- src/backend.go | 2 +- src/backend_fs.go | 4 ++-- src/backend_s3.go | 4 ++-- src/pages.go | 27 ++++++++++++++++++++------- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/backend.go b/src/backend.go index 038a68e..b82b7e3 100644 --- a/src/backend.go +++ b/src/backend.go @@ -10,7 +10,7 @@ import ( "time" ) -var errNotFound = errors.New("not found") +var ObjectNotFoundError = errors.New("not found") func splitBlobName(name string) []string { algo, hash, found := strings.Cut(name, "-") diff --git a/src/backend_fs.go b/src/backend_fs.go index ebabd99..eacba8e 100644 --- a/src/backend_fs.go +++ b/src/backend_fs.go @@ -100,7 +100,7 @@ func (fs *FSBackend) GetBlob( blobPath := filepath.Join(splitBlobName(name)...) stat, err := fs.blobRoot.Stat(blobPath) if errors.Is(err, os.ErrNotExist) { - err = fmt.Errorf("%w: %s", errNotFound, err.(*os.PathError).Path) + err = fmt.Errorf("%w: %s", ObjectNotFoundError, err.(*os.PathError).Path) return } else if err != nil { err = fmt.Errorf("stat: %w", err) @@ -171,7 +171,7 @@ func (b *FSBackend) ListManifests(ctx context.Context) (manifests []string, err func (fs *FSBackend) GetManifest(ctx context.Context, name string, opts GetManifestOptions) (*Manifest, error) { data, err := fs.siteRoot.ReadFile(name) if errors.Is(err, os.ErrNotExist) { - return nil, fmt.Errorf("%w: %s", errNotFound, err.(*os.PathError).Path) + return nil, fmt.Errorf("%w: %s", ObjectNotFoundError, err.(*os.PathError).Path) } else if err != nil { return nil, err } diff --git a/src/backend_s3.go b/src/backend_s3.go index 4c91593..687f3ee 100644 --- a/src/backend_s3.go +++ b/src/backend_s3.go @@ -299,7 +299,7 @@ func (s3 *S3Backend) GetBlob( cached, err = s3.blobCache.Get(ctx, name, otter.LoaderFunc[string, *CachedBlob](loader)) if err != nil { if errResp := minio.ToErrorResponse(err); errResp.Code == "NoSuchKey" { - err = fmt.Errorf("%w: %s", errNotFound, errResp.Key) + err = fmt.Errorf("%w: %s", ObjectNotFoundError, errResp.Key) } } else { reader = bytes.NewReader(cached.blob) @@ -433,7 +433,7 @@ func (l s3ManifestLoader) load(ctx context.Context, name string, oldManifest *Ca if err != nil { if errResp := minio.ToErrorResponse(err); errResp.Code == "NoSuchKey" { - err = fmt.Errorf("%w: %s", errNotFound, errResp.Key) + err = fmt.Errorf("%w: %s", ObjectNotFoundError, errResp.Key) return &CachedManifest{nil, 1, etag, err}, nil } else if errResp.StatusCode == http.StatusNotModified && oldManifest != nil { return oldManifest, nil diff --git a/src/pages.go b/src/pages.go index 6f98f97..c823a81 100644 --- a/src/pages.go +++ b/src/pages.go @@ -88,24 +88,31 @@ func getPage(w http.ResponseWriter, r *http.Request) error { return err } - indexManifestCh := make(chan *Manifest, 1) + type indexManifestResult struct { + manifest *Manifest + err error + } + indexManifestCh := make(chan indexManifestResult, 1) go func() { - manifest, _ := backend.GetManifest(r.Context(), makeWebRoot(host, ".index"), + manifest, err := backend.GetManifest(r.Context(), makeWebRoot(host, ".index"), GetManifestOptions{BypassCache: bypassCache}) - indexManifestCh <- manifest + indexManifestCh <- (indexManifestResult{manifest, err}) }() + err = nil sitePath = strings.TrimPrefix(r.URL.Path, "/") if projectName, projectPath, found := strings.Cut(sitePath, "/"); found { - projectManifest, err := backend.GetManifest(r.Context(), makeWebRoot(host, projectName), + var projectManifest *Manifest + projectManifest, err = backend.GetManifest(r.Context(), makeWebRoot(host, projectName), GetManifestOptions{BypassCache: bypassCache}) if err == nil { sitePath, manifest = projectPath, projectManifest } } - if manifest == nil { - manifest = <-indexManifestCh - if manifest == nil { + if manifest == nil && (err == nil || errors.Is(err, ObjectNotFoundError)) { + result := <-indexManifestCh + manifest, err = result.manifest, result.err + if manifest == nil && errors.Is(err, ObjectNotFoundError) { if found, fallbackErr := HandleWildcardFallback(w, r); found { return fallbackErr } else { @@ -115,6 +122,12 @@ func getPage(w http.ResponseWriter, r *http.Request) error { } } } + if err != nil { + ObserveError(err) // all storage errors must be reported + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "internal server error (%s)\n", err) + return err + } if r.Header.Get("Origin") != "" { // allow JavaScript code to access responses (including errors) even across origins