【四、http】go的http的文件下载

一、日常下载图片到本地

cpp 复制代码
//下载文件

func downloadfile(url, filename string) {
	r, err := http.Get(url)
	if err != nil {
		fmt.Println("err", err.Error())
	}

	defer r.Body.Close()

	f, err := os.Create(filename)
	if err != nil {
		fmt.Println("err", err.Error())
	}

	defer f.Close()

	n, err := io.Copy(f, r.Body)
	fmt.Println(n, err)
}

func main() {
	var url = "https://img-s-msn-com.akamaized.net/tenant/amp/entityid/AA1jjNfg.img?w=1920&h=1080&q=60&m=2&f=jpg"
	downloadfile(url, "test.jpg")
}

这里以一张图片为例子

从后台可以看到图片的url地址

修改main函数中的url地址,可以下载到本地

结果如下

二、显示文件下载进度

cpp 复制代码
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func downloadFile(url, filename string) {
	r, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	defer func() {_ = r.Body.Close()}()

	f, err := os.Create(filename)
	if err != nil {
		panic(err)
	}
	defer func() {_ = f.Close()}()

	n, err := io.Copy(f, r.Body)
	fmt.Println(n, err)
}

type Reader struct {
	io.Reader
	Total int64
	Current int64
}

func (r *Reader) Read(p []byte) (n int, err error){
	n, err = r.Reader.Read(p)

	r.Current += int64(n)
	fmt.Printf("\r进度 %.2f%%", float64(r.Current * 10000/ r.Total)/100)

	return
}

func DownloadFileProgress(url, filename string) {
	r, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	defer func() {_ = r.Body.Close()}()

	f, err := os.Create(filename)
	if err != nil {
		panic(err)
	}
	defer func() {_ = f.Close()}()

	reader := &Reader{
		Reader: r.Body,
		Total: r.ContentLength,
	}

	_, _ = io.Copy(f, reader)
}

func main() {
	// 自动文件下载,比如自动下载图片、压缩包
	url := "https://user-gold-cdn.xitu.io/2019/6/30/16ba8cb6465a6418?w=826&h=782&f=png&s=279620"
	filename := "poloxue.png"
	DownloadFileProgress(url, filename)
}

io.copy函数实现原理

cpp 复制代码
func Copy(dst Writer, src Reader) (written int64, err error) {
   // Copy 函数 调用了 copyBuffer 函数来实现
   return copyBuffer(dst, src, nil)
}

func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
   // 如果 源Reader 实现了 WriterTo 接口,直接调用该方法 将数据写入到 目标Writer 当中
   if wt, ok := src.(WriterTo); ok {
      return wt.WriteTo(dst)
   }
   // 同理,如果 目标Writer 实现了 ReaderFrom 接口,直接调用ReadFrom方法
   if rt, ok := dst.(ReaderFrom); ok {
      return rt.ReadFrom(src)
   }
   // 如果没有传入缓冲区,此时默认 创建一个 缓冲区
   if buf == nil {
      // 默认缓冲区 大小为 32kb
      size := 32 * 1024
      // 如果源Reader 为LimitedReader, 此时比较 可读数据数 和 默认缓冲区,取较小那个
      if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
         if l.N < 1 {
            size = 1
         } else {
            size = int(l.N)
         }
      }
      buf = make([]byte, size)
   }
   for {
      // 调用Read方法 读取数据
      nr, er := src.Read(buf)
      if nr > 0 {
         // 将数据写入到 目标Writer 当中
         nw, ew := dst.Write(buf[0:nr])
         // 判断写入是否 出现了 错误
         if nw < 0 || nr < nw {
            nw = 0
            if ew == nil {
               ew = errInvalidWrite
            }
         }
         // 累加 总写入数据
         written += int64(nw)
         if ew != nil {
            err = ew
            break
         }
         // 写入字节数 小于 读取字节数,此时报错
         if nr != nw {
            err = ErrShortWrite
            break
         }
      }
      if er != nil {
         if er != EOF {
            err = er
         }
         break
      }
   }
   return written, err
}func Copy(dst Writer, src Reader) (written int64, err error) {
   // Copy 函数 调用了 copyBuffer 函数来实现
   return copyBuffer(dst, src, nil)
}

func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
   // 如果 源Reader 实现了 WriterTo 接口,直接调用该方法 将数据写入到 目标Writer 当中
   if wt, ok := src.(WriterTo); ok {
      return wt.WriteTo(dst)
   }
   // 同理,如果 目标Writer 实现了 ReaderFrom 接口,直接调用ReadFrom方法
   if rt, ok := dst.(ReaderFrom); ok {
      return rt.ReadFrom(src)
   }
   // 如果没有传入缓冲区,此时默认 创建一个 缓冲区
   if buf == nil {
      // 默认缓冲区 大小为 32kb
      size := 32 * 1024
      // 如果源Reader 为LimitedReader, 此时比较 可读数据数 和 默认缓冲区,取较小那个
      if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
         if l.N < 1 {
            size = 1
         } else {
            size = int(l.N)
         }
      }
      buf = make([]byte, size)
   }
   for {
      // 调用Read方法 读取数据
      nr, er := src.Read(buf)
      if nr > 0 {
         // 将数据写入到 目标Writer 当中
         nw, ew := dst.Write(buf[0:nr])
         // 判断写入是否 出现了 错误
         if nw < 0 || nr < nw {
            nw = 0
            if ew == nil {
               ew = errInvalidWrite
            }
         }
         // 累加 总写入数据
         written += int64(nw)
         if ew != nil {
            err = ew
            break
         }
         // 写入字节数 小于 读取字节数,此时报错
         if nr != nw {
            err = ErrShortWrite
            break
         }
      }
      if er != nil {
         if er != EOF {
            err = er
         }
         break
      }
   }
   return written, err
}

从中可以看出,io.copy函数是通过read读取文件进行写入新创建的文件,因此,重写后的read函数除了实现原来的read功能,还增加了进度条功能。

相关推荐
heilai43 小时前
workerman的安装与使用
c++·websocket·http·php·phpstorm·visual studio code
ClareXi3 小时前
react项目通过http调用后端springboot服务最简单示例
spring boot·react.js·http
__AtYou__5 小时前
Golang | Leetcode Golang题解之第563题二叉树的坡度
leetcode·golang·题解
凡人的AI工具箱12 小时前
15分钟学 Go 第 49 天 :复杂项目开发
开发语言·人工智能·后端·算法·golang
杜杜的man13 小时前
【go从零单排】Random Numbers、Number Parsing
开发语言·python·golang
aiee15 小时前
Golang时间函数
开发语言·后端·golang
程序员小羊!16 小时前
HTTP&Tomcat&Servle之HTTP详解
网络协议·http·tomcat
还是转转16 小时前
Go开发指南- Goroutine
开发语言·golang
蜗牛沐雨16 小时前
Go语言中的`io.Pipe`:实现进程间通信的利器
开发语言·后端·golang·进程通信·pipe
杜杜的man16 小时前
【go从零单排】泛型(Generics)、链表
开发语言·链表·golang