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).
This commit is contained in:
Catherine
2025-11-11 05:59:08 +00:00
parent c4b3671a53
commit f9e142dd51
4 changed files with 25 additions and 12 deletions

View File

@@ -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, "-")

View File

@@ -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
}

View File

@@ -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

View File

@@ -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