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

相关推荐
一只很酸de橘子4 小时前
关于https请求丢字符串导致收到报文解密失败问题
网络协议·http·https
大学生亨亨4 小时前
go语言八股文(五)
开发语言·笔记·golang
raoxiaoya4 小时前
同时安装多个版本的golang
开发语言·后端·golang
桃花仙丶8 小时前
iOS/Flutter混合开发之PlatformView配置与使用
flutter·ios·xcode·swift·dart
mxbb.8 小时前
我的HTTP和HTTPS
网络协议·http·https
Zz_waiting.9 小时前
网络原理 - 10(HTTP/HTTPS - 1)
网络·http·https
专注API从业者9 小时前
《Go 语言高并发爬虫开发:淘宝商品 API 实时采集与 ETL 数据处理管道》
开发语言·后端·爬虫·golang
洁✘10 小时前
web基础与http协议
网络·网络协议·http
我要喝可乐!12 小时前
OkHttp源码梳理
网络协议·http·okhttp
类似不类似13 小时前
快速配置linux远程开发-go语言
开发语言·后端·golang