Serving partial content is non-trivial. See Byte serving on wikipedia for an introduction. You have to handle specific status codes and headers (both request and response), which is not too hard but you shouldn't waste your time doing that yourself.
If the content to serve (or serve from) is a file, you may use http.ServeFile()
just as you mentioned, which handles serving partial content (Range requests).
If the content to be served is not present as a file, then http.ServeContent()
is your friend:
func ServeContent(w ResponseWriter, req *Request,
name string, modtime time.Time, content io.ReadSeeker)
And yes, it also handles serving partial content (Range requests):
The main benefit of ServeContent over io.Copy is that it handles Range requests properly, sets the MIME type, and handles If-Modified-Since requests.
All you need to do is provide an io.ReadSeeker
"view" of your content, this is required so that the implementation can "jump" to the part that is requested by the client, the part that needs to be served. You might ask: how to do that?
The bytes
package contains a type that implements io.ReadSeeker
: bytes.Reader
.
So for example if you have the content as a []byte
, you may obtain an io.ReadSeeker
like this:
var content []byte
// fill content
r := bytes.NewReader(content)
And what if you don't have the whole content as a []byte
? One option is to provide a value of your own type that implements io.ReadSeeker
.
io.ReadSeeker
is:
type ReadSeeker interface {
Reader
Seeker
}
io.Reader
contains one method:
Read(p []byte) (n int, err error)
io.Seeker
also contains one method:
Seek(offset int64, whence int) (int64, error)
Your content is accessible somewhere, somehow, you know how. Seek()
is called to let you know what part (position) is required from your content, and Read()
is called so you can populate the passed slice (to provide the actual content). Note that these methods may be called multiple times, so you have to keep track where you are in your content (source). If you choose to go down this path, please read the doc of the linked interfaces to make sure you meet the "general contract" of the interfaces to avoid surprise errors.
ServeContent
is non-trivial when you have to implementio.SeekReader
yourself. For example, the GCS client doesn't appear to support it: github.com/googleapis/google-cloud-go/issues/1124 and while I'm probably doing it wrong, I really want to supportNewRangeReader
so I had to write the code to do that: WHEW! – Thatch