11package util
22
33import (
4+ "bufio"
45 "bytes"
56 "compress/gzip"
67 "crypto/md5"
@@ -9,14 +10,19 @@ import (
910 "hash/crc32"
1011 "io"
1112 "io/ioutil"
13+ "net"
1214 "net/http"
15+ "net/url"
1316 "os"
1417 "runtime"
1518 "runtime/debug"
19+ "strconv"
1620 "strings"
21+ "sync"
1722 "time"
1823 "unsafe"
1924
25+ "github.com/pkg/errors"
2026 log "github.com/sirupsen/logrus"
2127)
2228
@@ -130,4 +136,224 @@ func MustMd5(text string) string {
130136 panic (err )
131137 }
132138 return fmt .Sprintf ("%x" , w .Sum (nil ))
133- }
139+ }
140+
141+ var (
142+ client = & http.Client {
143+ Timeout : time .Second * 120 ,
144+ Transport : & http.Transport {
145+ Proxy : func (request * http.Request ) (u * url.URL , e error ) {
146+ if Proxy == "" {
147+ return http .ProxyFromEnvironment (request )
148+ }
149+ return url .Parse (Proxy )
150+ },
151+ DialContext : (& net.Dialer {
152+ Timeout : 30 * time .Second ,
153+ KeepAlive : 30 * time .Second ,
154+ }).DialContext ,
155+ ForceAttemptHTTP2 : true ,
156+ IdleConnTimeout : 90 * time .Second ,
157+ TLSHandshakeTimeout : 10 * time .Second ,
158+ ExpectContinueTimeout : 1 * time .Second ,
159+ MaxConnsPerHost : 0 ,
160+ MaxIdleConns : 0 ,
161+ MaxIdleConnsPerHost : 999 ,
162+ },
163+ }
164+ Proxy string
165+
166+ ErrOverSize = errors .New ("oversize" )
167+
168+ UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66"
169+ )
170+
171+ func DownloadFile (url , path string , limit int64 , headers map [string ]string ) error {
172+ file , err := os .OpenFile (path , os .O_WRONLY | os .O_CREATE , 0666 )
173+ if err != nil {
174+ return err
175+ }
176+ defer file .Close ()
177+ req , err := http .NewRequest ("GET" , url , nil )
178+ if err != nil {
179+ return err
180+ }
181+ if headers != nil {
182+ for k , v := range headers {
183+ req .Header .Set (k , v )
184+ }
185+ }
186+ if _ , ok := headers ["User-Agent" ]; ok {
187+ req .Header ["User-Agent" ] = []string {UserAgent }
188+ }
189+ resp , err := client .Do (req )
190+ if err != nil {
191+ return err
192+ }
193+ defer resp .Body .Close ()
194+ if limit > 0 && resp .ContentLength > limit {
195+ return ErrOverSize
196+ }
197+ _ , err = io .Copy (file , resp .Body )
198+ if err != nil {
199+ return err
200+ }
201+ return nil
202+ }
203+
204+ func DownloadFileMultiThreading (url , path string , limit int64 , threadCount int , headers map [string ]string ) error {
205+ if threadCount < 2 {
206+ return DownloadFile (url , path , limit , headers )
207+ }
208+ type BlockMetaData struct {
209+ BeginOffset int64
210+ EndOffset int64
211+ DownloadedSize int64
212+ }
213+ var blocks []* BlockMetaData
214+ var contentLength int64
215+ errUnsupportedMultiThreading := errors .New ("unsupported multi-threading" )
216+ // 初始化分块或直接下载
217+ initOrDownload := func () error {
218+ copyStream := func (s io.ReadCloser ) error {
219+ file , err := os .OpenFile (path , os .O_WRONLY | os .O_CREATE , 0666 )
220+ if err != nil {
221+ return err
222+ }
223+ defer file .Close ()
224+ if _ , err = io .Copy (file , s ); err != nil {
225+ return err
226+ }
227+ return errUnsupportedMultiThreading
228+ }
229+ req , err := http .NewRequest ("GET" , url , nil )
230+ if err != nil {
231+ return err
232+ }
233+ if headers != nil {
234+ for k , v := range headers {
235+ req .Header .Set (k , v )
236+ }
237+ }
238+ if _ , ok := headers ["User-Agent" ]; ok {
239+ req .Header ["User-Agent" ] = []string {UserAgent }
240+ }
241+ req .Header .Set ("range" , "bytes=0-" )
242+ resp , err := client .Do (req )
243+ if err != nil {
244+ return err
245+ }
246+ if resp .StatusCode < 200 || resp .StatusCode >= 300 {
247+ return errors .New ("response status unsuccessful: " + strconv .FormatInt (int64 (resp .StatusCode ), 10 ))
248+ }
249+ if resp .StatusCode == 200 {
250+ if limit > 0 && resp .ContentLength > limit {
251+ return ErrOverSize
252+ }
253+ return copyStream (resp .Body )
254+ }
255+ if resp .StatusCode == 206 {
256+ contentLength = resp .ContentLength
257+ if limit > 0 && resp .ContentLength > limit {
258+ return ErrOverSize
259+ }
260+ blockSize := func () int64 {
261+ if contentLength > 1024 * 1024 {
262+ return (contentLength / int64 (threadCount )) - 10
263+ } else {
264+ return contentLength
265+ }
266+ }()
267+ if blockSize == contentLength {
268+ return copyStream (resp .Body )
269+ }
270+ var tmp int64
271+ for tmp + blockSize < contentLength {
272+ blocks = append (blocks , & BlockMetaData {
273+ BeginOffset : tmp ,
274+ EndOffset : tmp + blockSize - 1 ,
275+ })
276+ tmp += blockSize
277+ }
278+ blocks = append (blocks , & BlockMetaData {
279+ BeginOffset : tmp ,
280+ EndOffset : contentLength - 1 ,
281+ })
282+ return nil
283+ }
284+ return errors .New ("unknown status code." )
285+ }
286+ // 下载分块
287+ downloadBlock := func (block * BlockMetaData ) error {
288+ req , _ := http .NewRequest ("GET" , url , nil )
289+ file , err := os .OpenFile (path , os .O_WRONLY | os .O_CREATE , 0666 )
290+ if err != nil {
291+ return err
292+ }
293+ defer file .Close ()
294+ _ , _ = file .Seek (block .BeginOffset , io .SeekStart )
295+ writer := bufio .NewWriter (file )
296+ defer writer .Flush ()
297+ if headers != nil {
298+ for k , v := range headers {
299+ req .Header .Set (k , v )
300+ }
301+ }
302+ if _ , ok := headers ["User-Agent" ]; ok {
303+ req .Header ["User-Agent" ] = []string {UserAgent }
304+ }
305+ req .Header .Set ("range" , "bytes=" + strconv .FormatInt (block .BeginOffset , 10 )+ "-" + strconv .FormatInt (block .EndOffset , 10 ))
306+ resp , err := client .Do (req )
307+ if err != nil {
308+ return err
309+ }
310+ defer resp .Body .Close ()
311+ if resp .StatusCode < 200 || resp .StatusCode >= 300 {
312+ return errors .New ("response status unsuccessful: " + strconv .FormatInt (int64 (resp .StatusCode ), 10 ))
313+ }
314+ var buffer = make ([]byte , 1024 )
315+ i , err := resp .Body .Read (buffer )
316+ for {
317+ if err != nil && err != io .EOF {
318+ return err
319+ }
320+ i64 := int64 (len (buffer [:i ]))
321+ needSize := block .EndOffset + 1 - block .BeginOffset
322+ if i64 > needSize {
323+ i64 = needSize
324+ err = io .EOF
325+ }
326+ _ , e := writer .Write (buffer [:i64 ])
327+ if e != nil {
328+ return e
329+ }
330+ block .BeginOffset += i64
331+ block .DownloadedSize += i64
332+ if err == io .EOF || block .BeginOffset > block .EndOffset {
333+ break
334+ }
335+ i , err = resp .Body .Read (buffer )
336+ }
337+ return nil
338+ }
339+
340+ if err := initOrDownload (); err != nil {
341+ if err == errUnsupportedMultiThreading {
342+ return nil
343+ }
344+ return err
345+ }
346+ wg := sync.WaitGroup {}
347+ wg .Add (len (blocks ))
348+ var lastErr error
349+ for i := range blocks {
350+ go func (b * BlockMetaData ) {
351+ defer wg .Done ()
352+ if err := downloadBlock (b ); err != nil {
353+ lastErr = err
354+ }
355+ }(blocks [i ])
356+ }
357+ wg .Wait ()
358+ return lastErr
359+ }
0 commit comments