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) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment