How to serve http partial content with Go?
I have a webserver written in go and I'm serving some audio files from different sources (local, other servers, S3). I want to enable serving partial content for this files so the HTML audio tags are able to seek and loop.
How can I achive this? I know that the http
package ServeContent
function does this but how can I do it by serving the files myself? I need to do it without this so that I can serve files from different sources with the same handler.
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.