labstack/echo · error

file does not implement io.ReadSeeker

Error message

file does not implement io.ReadSeeker

What it means

Returned at request time by serveFile in the Static middleware when the opened fs.File does not satisfy io.ReadSeeker. http.ServeContent needs to Seek (to support Range requests and determine Content-Length), so a file that only implements Read cannot be served. The standard os.File and embed.FS files implement ReadSeeker; custom fs.FS implementations (e.g., streaming or generated readers) may not.

Source

Thrown at middleware/static.go:319

				defer index.Close()

				info, err = index.Stat()
				if err != nil {
					return err
				}

				return serveFile(c, index, info)
			}

			return serveFile(c, file, info)
		}
	}, nil
}

func serveFile(c *echo.Context, file fs.File, info os.FileInfo) error {
	ff, ok := file.(io.ReadSeeker)
	if !ok {
		return errors.New("file does not implement io.ReadSeeker")
	}
	http.ServeContent(c.Response(), c.Request(), info.Name(), info.ModTime(), ff)
	return nil
}

func listDir(t *template.Template, pathInFs string, filesystem fs.FS, res http.ResponseWriter) error {
	files, err := fs.ReadDir(filesystem, pathInFs)
	if err != nil {
		return fmt.Errorf("static middleware failed to read directory for listing: %w", err)
	}

	// Create directory index
	res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
	data := struct {
		Name  string
		Files []any
	}{
		Name: pathInFs,

View on GitHub (pinned to 05489dc173)

Solutions

  1. Ensure your custom fs.File implementation also implements Seek(offset, whence) (int64, error) so it satisfies io.ReadSeeker.
  2. If the source truly cannot seek, pre-buffer the content into a bytes.Reader (which implements ReadSeeker) before returning from Open.
  3. Use embed.FS or the default os-based filesystem, whose files already implement ReadSeeker.
  4. If you cannot change the filesystem, serve the content yourself with c.Stream / c.Blob instead of the Static middleware.

Example fix

// before: custom file only implements Read
type streamFile struct{ *bytes.Reader }
// after: embed a *bytes.Reader so Read+Seek are satisfied
type seekableFile struct{ *bytes.Reader }
func (f *seekableFile) Stat() (fs.FileInfo, error) { return fstat, nil }
func (f *seekableFile) Close() error { return nil }
Defensive patterns

Strategy: type-guard

Type guard

func canServeContent(f fs.File) bool {
    _, ok := f.(io.ReadSeeker)
    return ok
}

// usage before http.ServeContent:
// if !canServeContent(file) { return echo.NewHTTPError(http.StatusInternalServerError, "file not seekable") }

Prevention

When it happens

Trigger: Configuring StaticConfig with a custom fs.FS whose Open returns a file lacking a Seek method; or wrapping the default filesystem with a decorator that hides Seek. The error is returned from the handler during a request that resolves to a file.

Common situations: Using a custom filesystem backed by object storage / a streaming reader / a generated asset source where files are not seekable; or an fs.FS adapter that returns a wrapper type without forwarding Seek.

Related errors


AI-assisted analysis of labstack/echo@05489dc173 (2026-08-04). Data as JSON: /data/errors/e5a47b8045c7da6f.json. Report an issue: GitHub.