Skip to content

Instantly share code, notes, and snippets.

@yujp
Created March 18, 2020 13:11
Show Gist options
  • Save yujp/035b1079ee66678708ade646f7bb29a4 to your computer and use it in GitHub Desktop.
Save yujp/035b1079ee66678708ade646f7bb29a4 to your computer and use it in GitHub Desktop.
package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"math"
"net/http"
"os"
"path/filepath"
"strconv"
"sync"
"time"
)
func main() {
start := time.Now()
if len(os.Args) < 2 {
fmt.Printf("Usage: %s <URL>\n", os.Args[0])
return
}
URL := os.Args[1]
fileName := filepath.Base(URL)
fo, err := os.Create(fileName)
if err != nil {
panic(err)
}
defer fo.Close()
w := bufio.NewWriter(fo)
defer w.Flush()
downloadURL(URL, w, 0, 100)
fmt.Printf("finished: %v\n", time.Since(start))
}
func downloadURL(URL string, writer io.Writer, chunkSize uint64, maxRoutines uint64) error {
res, _ := http.Head(URL)
clen, _ := strconv.ParseUint(res.Header.Get("Content-Length"), 10, 64)
if chunkSize == 0 {
chunkSize = 2 * 1024 * 1024
}
if maxRoutines == 0 {
maxRoutines = 10
}
totalChunks := uint64(math.Ceil(float64(clen) / float64(chunkSize)))
lastChunkSize := clen % chunkSize
stacks := make([][]byte, totalChunks)
sem := make(chan struct{}, maxRoutines)
var wg sync.WaitGroup
var i uint64
for i = 0; i < totalChunks; i++ {
wg.Add(1)
sem <- struct{}{} // ensure
min := chunkSize * i
max := chunkSize * (i + 1)
if i == totalChunks-1 {
max = min + lastChunkSize
}
go func(min uint64, max uint64, idx uint64) {
defer func() {
wg.Done()
<-sem
}()
rng := fmt.Sprintf("bytes=%d-%d", min, max-1)
fmt.Printf("%04d: %s\n", idx, rng)
req, _ := http.NewRequest(http.MethodGet, URL, nil)
req.Header.Add("Range", rng)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
stacks[idx] = b
}(min, max, i)
}
wg.Wait()
for _, body := range stacks {
writer.Write(body)
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment