go 从url下载大文件(支持断点续传)


package main

import (
	"bufio"
	"errors"
	"fmt"
	"github.com/go-emix/utils"
	"io"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"time"
)

func down(ur, dir, fn string, to time.Duration, onErr func(err error)) {
	if !utils.FileIsExist(dir) {
		err := os.MkdirAll(dir, os.ModePerm)
		if err != nil {
			fmt.Println("mkdir "+dir+" err:", err.Error())
			onErr(err)
			return
		}
	}
	dfn := dir + "/" + fn
	var file *os.File
	var size int64
	if utils.FileIsExist(dfn) {
		fi, err := os.OpenFile(dfn, os.O_RDWR, os.ModePerm)
		if err != nil {
			fmt.Println(fn, "open err:", err)
			onErr(err)
			return
		}
		stat, _ := fi.Stat()
		size = stat.Size()
		sk, err := fi.Seek(size, 0)
		if err != nil {
			fmt.Println(fn, "seek err:", err)
			_ = fi.Close()
			onErr(err)
			return
		}
		if sk != size {
			fmt.Printf("%s seek length not equal file size,"+
				"seek=%d,size=%d\n", fn, sk, size)
			_ = fi.Close()
			onErr(errors.New("seek length not equal file size"))
			return
		}
		file = fi
	} else {
		create, err := os.Create(dfn)
		if err != nil {
			fmt.Println(fn, "create err:", err)
			onErr(err)
			return
		}
		file = create
	}
	client := &http.Client{}
	client.Timeout = to
	request := http.Request{}
	request.Method = http.MethodGet
	if size != 0 {
		header := http.Header{}
		header.Set("Range", "bytes="+strconv.FormatInt(size, 10)+"-")
		request.Header = header
	}
	parse, err := url.Parse(ur)
	if err != nil {
		fmt.Println(ur, "url err:", err)
		onErr(err)
		return
	}
	request.URL = parse
	get, err := client.Do(&request)
	if err != nil {
		fmt.Println(ur, "get err:", err)
		onErr(err)
		return
	}
	defer func() {
		err := get.Body.Close()
		if err != nil {
			fmt.Println(fn, "body close:", err.Error())
			onErr(err)
		}
		err = file.Close()
		if err != nil {
			fmt.Println(fn, "file close:", err.Error())
			onErr(err)
		}
	}()
	if get.ContentLength == 0 {
		fmt.Println(fn, "already downloaded")
		return
	}
	body := get.Body
	writer := bufio.NewWriter(file)
	bs := make([]byte, 10*1024*1024) //每次读取的最大字节数,不可为0
	for {
		var read int
		read, err = body.Read(bs)
		if err != nil {
			if err != io.EOF {
				fmt.Println(fn, "read err:"+err.Error())
				onErr(err)
			} else {
				err = nil
			}
			break
		}
		_, err = writer.Write(bs[:read])
		if err != nil {
			fmt.Println(fn, "write err:"+err.Error())
			onErr(err)
			break
		}
	}
	if err != nil {
		return
	}
	err = writer.Flush()
	if err != nil {
		fmt.Println(fn, "writer flush:", err.Error())
		onErr(err)
		return
	}
	fmt.Println(fn, "download success")
}

go test

 

package main

import (
	"fmt"
	"testing"
	"time"
)

func TestDown(t *testing.T) {
	//ur := "https://dl.google.com/go/go1.15.2.linux-amd64.tar.gz"
	ur := "https://dl.google.com/go/go1.15.2.src.tar.gz"
	fn := "src.7z"
	//fn := "linux.7z"
	go down(ur, "download", fn, time.Hour, func(err error) {

	})
    //打点计时器,监听显示
	tick := time.Tick(time.Minute)
	for range tick {
		fmt.Println("...")
	}
}

ps : 不需先创建目录,会自动创建;onErr是错误回调函数

你可能感兴趣的:(go,go,下载大文件,断点续传)