Created
November 5, 2018 15:30
-
-
Save xeoncross/666e448c9edea957b76b653f3d6215d9 to your computer and use it in GitHub Desktop.
Concurrent HTTP file downloader in Golang. https://www.reddit.com/r/golang/comments/9ttjb9/how_to_download_single_file_concurrently/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package phttp | |
import ( | |
"context" | |
"errors" | |
"fmt" | |
"io" | |
"net/http" | |
"golang.org/x/net/context/ctxhttp" | |
"golang.org/x/sync/errgroup" | |
) | |
func PGet(ctx context.Context, client *http.Client, w io.WriterAt, n int, url string) error { | |
resp, err := ctxhttp.Head(ctx, client, url) | |
if err != nil { | |
return err | |
} | |
resp.Body.Close() | |
if resp.StatusCode != http.StatusOK { | |
return fmt.Errorf("server responded with %d status code", resp.StatusCode) | |
} | |
if resp.Header.Get("Accept-Ranges") != "bytes" { | |
return errors.New("server does not support range requests") | |
} | |
if resp.ContentLength < 0 { | |
return errors.New("server sent invalid Content-Length header") | |
} | |
sectionLen := resp.ContentLength / int64(n) | |
wg, ctx := errgroup.WithContext(ctx) | |
for off := int64(0); off < resp.ContentLength; off += sectionLen { | |
off := off | |
lim := off + sectionLen | |
if lim >= resp.ContentLength { | |
lim = resp.ContentLength | |
} | |
wg.Go(func() error { return getPart(ctx, client, w, url, off, lim) }) | |
} | |
return wg.Wait() | |
} | |
func getPart(ctx context.Context, client *http.Client, w io.WriterAt, url string, off, lim int64) error { | |
req, err := http.NewRequest("GET", url, nil) | |
if err != nil { | |
return err | |
} | |
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", off, lim)) | |
resp, err := ctxhttp.Do(ctx, client, req) | |
if err != nil { | |
return err | |
} | |
defer resp.Body.Close() | |
if resp.StatusCode != http.StatusPartialContent { | |
return fmt.Errorf("server responded with %d status code, expected %d", resp.StatusCode, http.StatusPartialContent) | |
} | |
_, err = io.Copy(newSectionWriter(w, off), resp.Body) | |
return err | |
} | |
func newSectionWriter(w io.WriterAt, off int64) *sectionWriter { | |
return §ionWriter{w, off} | |
} | |
type sectionWriter struct { | |
w io.WriterAt | |
off int64 | |
} | |
func (w *sectionWriter) Write(p []byte) (n int, err error) { | |
n, err = w.w.WriteAt(p, w.off) | |
w.off += int64(n) | |
return | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment