Skip to content

Instantly share code, notes, and snippets.

@vardius
Created May 27, 2020 07:21
Show Gist options
  • Save vardius/ebdbd84d23d6e794edf322f7fd9e02fa to your computer and use it in GitHub Desktop.
Save vardius/ebdbd84d23d6e794edf322f7fd9e02fa to your computer and use it in GitHub Desktop.
Go download stream response
// https://rafallorenz.com/go/go-http-stream-download/
package main
import (
"errors"
"io"
"io/ioutil"
"net/http"
"time"
)
type stream struct {
chunk []byte
i int64 // current reading index
fetch func(offset int64) []byte // fetch callback
totalSize func() int64 // total size callback
}
func (s *stream) Read(b []byte) (n int, err error) {
if len(s.chunk) == 0 {
return 0, io.EOF
}
// simply copy our chunk
n = copy(b, s.chunk)
s.i += int64(n)
return
}
func (s *stream) Seek(offset int64, whence int) (int64, error) {
var abs int64
switch whence {
case io.SeekStart:
abs = offset
case io.SeekCurrent:
abs = s.i + offset
case io.SeekEnd:
abs = s.totalSize() + offset
default:
return 0, errors.New("stream..Reader.Seek: invalid whence")
}
if abs < 0 {
return 0, errors.New("stream..Reader.Seek: negative position")
}
s.i = abs
s.chunk = s.fetch(abs) // fetch chunk and set to buffer
return abs, nil
}
func downloadHandler(w http.ResponseWriter, r *http.Request) {
resp, err := http.Get("https://golangcode.com/logo.svg")
if err != nil {
http.Error(w, "could not write response", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
http.Error(w, "could not read body", http.StatusInternalServerError)
return
}
s := stream{
fetch: func(offset int64) []byte {
// for this example we will just get our chunk from buffer we already have
// in real life you probably would fetch it from some other place
// or generate it
return body[offset:]
},
totalSize: func() int64 {
// total size for ServeContent to parse ranges
// TODO: handle unknown total size
return int64(len(body))
},
}
// we set the type as we know it and ServeContent doesnt have to guess it
// we as well do not want it to return whole content at once if it can not guess the type
w.Header().Set("Content-Type", "image/svg+xml")
http.ServeContent(w, r, "logo.svg", time.Now(), &s)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", downloadHandler)
http.ListenAndServe(":3000", mux)
}
@4-FLOSS-Free-Libre-Open-Source-Software
Copy link

Thanks for the example

@vardius
Copy link
Author

vardius commented Dec 6, 2021

@4-FLOSS-Free-Libre-Open-Source-Software Are you correctly setting request's Range header ? please see checkPreconditions which is used to determine chunk for served content

func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) {
	setLastModified(w, modtime)
	done, rangeReq := checkPreconditions(w, r, modtime)
	if done {
		return
	}

...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment