【四、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功能,还增加了进度条功能。

相关推荐
无休居士2 小时前
HTTPS的单向认证和双向认证是什么?有什么区别?
网络协议·http·https·双向认证·单向认证
fcopy2 小时前
Golang项目:实现生产者消费者模式
缓存·golang
桃园码工3 小时前
第一章:Go 语言概述 1.什么是 Go 语言? --Go 语言轻松入门
开发语言·后端·golang
桃园码工4 小时前
第一章:Go 语言概述 2.安装和配置 Go 开发环境 --Go 语言轻松入门
开发语言·后端·golang
fcopy4 小时前
Golang项目:实现一个内存缓存系统
缓存·golang
桃始笑叶始泪5 小时前
HTTP中GET和POST的区别是什么?
网络·网络协议·http
hummhumm5 小时前
第 36 章 - Go语言 服务网格
java·运维·前端·后端·python·golang·java-ee
凡人的AI工具箱5 小时前
40分钟学 Go 语言高并发:Pipeline模式(一)
开发语言·后端·缓存·架构·golang
sxy1993sxy20186 小时前
HTTP请求失败调试过程 -20241126
网络·网络协议·http
搬砖的果果7 小时前
HTTP代理是什么,主要用来干嘛?
网络·python·网络协议·tcp/ip·http