Skip to content

proposal: net/http: ServeContentStream #14896

Closed
@colinmarc

Description

@colinmarc

net/http.ServeContent is an extremely useful utility for serving byte blobs and files. However, it requires an io.ReadSeeker with a valid Seek, so that it can a) determine the total size and b) seek around in the file to serve content ranges.

I recently found myself in a situation where I had an io.Reader, and really wanted to be able to handle Accept-Range, If-Not-Modified, etc. I had the total size and modtime ahead of time, so that was taken care of. But because ServeContent takes an io.ReadSeeker, I was left with two choices: either wrap the stream in an object that can do a fake "seek", or reimplement stuff like Accept-Range parsing, which is super fiddly.

I think my situation probably isn't that uncommon - any time you're serving data from an underlying store or database which supports streaming data off disk, but doesn't let you seek around in it, and you want to serve that over HTTP with Accept-Range support, you end up with this problem. To that end, I'd like to propose a sister method to ServeContent, maybe something like:

func ServeContentStream(w ResponseWriter, req *Request, name string, modtime time.Time, size int64, content io.Reader)

The implementation should be pretty simple. serveContent, which ServeContent backends to, already accepts a size argument (or more accurately, a sizeFunc). It could be changed to take an io.Reader as well, and attempt to type-assert the reader to an io.Seeker when and if it needs to. If that fails, it could discard bytes off the stream in order to serve successive ranges.

Note that the bits that require Seek are only for Accept-Range, where the ranges are known in advance and can be ordered; a backwards seek is never required. Discarding bytes is obviously not ideal; but it's way better than buffering the whole blob into memory to wrap it in a bytes.Reader.

Obviously, I'd be happy to put in the work myself to make this change! Just filing an issue first since that's what the docs say to do. =)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions