goroutine&waitgroup下载文件
0.1、索引
https://blog.waterflow.link/articles/1663078266267
当我们下载一个大文件的时候,会因为下载时间太久而超时或者出错。那么我么我们可以利用goroutine的特性并发分段的去请求下载资源。
1、Accept-Ranges
首先下载链接需要在响应中返回Accept-Ranges,并且它的值不为 “none”,那么该服务器支持范围请求。比如我们可以利用HEAD请求来进行检测
...
// head请求获取url的header
head, err := http.Head(url)
if err != nil {
return err
}
// 判断url是否支持指定范围请求及哪种类型的分段请求
if head.Header.Get("Accept-Ranges") != "bytes" {
return errors.New("not support range download")
}
...
我们可以使用curl命令看下head头
curl -I https://agritrop.cirad.fr/584726/1/Rapport.pdf
HTTP/1.1 200 OK
Date: Tue, 13 Sep 2022 13:52:08 GMT
Server: HTTPD
Strict-Transport-Security: max-age=63072000
X-Content-Type-Options: nosniff
X-Frame-Options: sameorigin
Content-MD5: K4j+rsagurPwGP/5cm8k8Q==
Last-Modified: Tue, 04 Jul 2017 08:26:16 GMT
Expires: Wed, 13 Sep 2023 13:52:08 GMT
Content-Disposition: inline; filename=Rapport.pdf
Accept-Ranges: bytes # 允许范围请求,单位是字节
Content-Length: 6659798 # 文件的完整大小
Content-Type: application/pdf
X-XSS-Protection: 1; mode=block
X-Permitted-Cross-Domain-Policies: none
Cache-Control: public
其中,Accept-Ranges: bytes 表示界定范围的单位是 bytes 。这里 Content-Length也是有效信息,因为它提供了文件的完整大小。
2、Range
假如服务器支持范围请求的话,你可以使用 Range 首部来生成该类请求。该首部指示服务器应该返回文件的哪一或哪几部分。
...
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
fmt.Println("初始化request失败:", err)
return
}
rangeL := fmt.Sprintf("bytes=%d-%d", start, end)
fmt.Println("字符范围:", rangeL)
// 获取制定范围的数据
req.Header.Add("Range", rangeL)
res, err := client.Do(req)
...
单一范围
我们可以请求资源的某一部分。这次我们依然用 cURL 来进行测试。"-H" 选项可以在请求中追加一个首部行,在这个例子中,是用 Range 首部来请求图片文件的前 1024 个字节。
curl https://agritrop.cirad.fr/584726/1/Rapport.pdf -i -H "Range: bytes=0-1023"
HTTP/1.1 206 Partial Content
Date: Tue, 13 Sep 2022 14:00:47 GMT
Server: HTTPD
Strict-Transport-Security: max-age=63072000
X-Content-Type-Options: nosniff
X-Frame-Options: sameorigin
Content-MD5: K4j+rsagurPwGP/5cm8k8Q==
Last-Modified: Tue, 04 Jul 2017 08:26:16 GMT
Expires: Wed, 13 Sep 2023 14:00:47 GMT
Content-Disposition: inline; filename=Rapport.pdf
Accept-Ranges: bytes
Content-Range: bytes 0-1023/6659798 # 返回指定的字节
Content-Length: 1024
Content-Type: application/pdf
X-XSS-Protection: 1; mode=block
X-Permitted-Cross-Domain-Policies: none
Cache-Control: public
Content-Range表示请求的资源在整个资源中的位置,这个时候Content-Length就不是表示整个资源的大小,而是请求资源的大小。
多重范围
我们也可以请求多个范围,只需要在Range中指定多个即可
curl https://agritrop.cirad.fr/584726/1/Rapport.pdf -i -H "Range: bytes=0-50, 100-150"
HTTP/1.1 206 Partial Content
Date: Tue, 13 Sep 2022 14:04:53 GMT
Server: HTTPD
Strict-Transport-Security: max-age=63072000
X-Content-Type-Options: nosniff
X-Frame-Options: sameorigin
Content-MD5: K4j+rsagurPwGP/5cm8k8Q==
Last-Modified: Tue, 04 Jul 2017 08:26:16 GMT
Expires: Wed, 13 Sep 2023 14:04:53 GMT
Content-Disposition: inline; filename=Rapport.pdf
Accept-Ranges: bytes
Content-Length: 312
Content-Type: multipart/byteranges; boundary=4876db1cd4aa85af6
X-XSS-Protection: 1; mode=block
X-Permitted-Cross-Domain-Policies: none
Cache-Control: public
--4876db1cd4aa85af6
Content-type: application/pdf
Content-range: bytes 0-50/6659798
内容
--4876db1cd4aa85af6
Content-type: application/pdf
Content-range: bytes 100-150/6659798
内容
--4876db1cd4aa85af6--
服务器返回 206 Partial Content 状态码和 Content-Type:multipart/byteranges; boundary=3d6b6a416f9b5 头部,Content-Type:multipart/byteranges 表示这个响应有多个 byterange。每一部分 byterange 都有他自己的 Content-type 头部和 Content-Range,并且使用 boundary 参数对 body 进行划分。
3、goroutine
我们代码中通过获取Contetn-Length总大小,和spPart分成了3部分,通过goroutine进行并行的单一范围请求。然后把最终请求的结果保存在临时文件。之后再把这3部分内容统一保存到最终的文件中
具体代码如下:
package main
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"sync"
)
// 通过Content-Length分成3部分并发执行
var spPart = 3
// 任务编排控制
var wg sync.WaitGroup
func main() {
url := "https://agritrop.cirad.fr/584726/1/Rapport.pdf"
err := DownloadFile(url, "rapport.pdf")
if err != nil {
panic(err)
}
}
func DownloadFile(url string, filename string) error {
if strings.TrimSpace(url) == "" {
return nil
}
// head请求获取url的header
head, err := http.Head(url)
if err != nil {
return err
}
// 判断url是否支持指定范围请求及哪种类型的分段请求
if head.Header.Get("Accept-Ranges") != "bytes" {
return errors.New("not support range download")
}
contentLen, err := strconv.Atoi(head.Header.Get("Content-Length"))
if err != nil {
return err
}
offset := contentLen / spPart
for i := 0; i < spPart; i++ {
wg.Add(1)
start := offset * i
end := offset * (i + 1)
name := fmt.Sprintf("part%d", i)
go rangeDownload(url, name, start, end)
}
wg.Wait()
out, err := os.Create(filename)
if err != nil {
return err
}
defer out.Close()
for i := 0; i < spPart; i++ {
name := fmt.Sprintf("part%d", i)
file, err := ioutil.ReadFile(name)
if err != nil {
return err
}
out.WriteAt(file, int64(i*offset))
if err := os.Remove(name); err != nil {
return err
}
}
return nil
}
func rangeDownload(url string, name string, start int, end int) {
defer wg.Done()
client := http.Client{}
file, err := os.Create(name)
if err != nil {
fmt.Println("创建文件失败:", err)
return
}
defer file.Close()
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
fmt.Println("初始化request失败:", err)
return
}
rangeL := fmt.Sprintf("bytes=%d-%d", start, end)
fmt.Println("字符范围:", rangeL)
// 获取制定范围的数据
req.Header.Add("Range", rangeL)
res, err := client.Do(req)
if err != nil {
fmt.Println("发起http请求失败:", err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println("读取返回体失败:", err)
return
}
_, err = file.Write(body)
if err != nil {
fmt.Println("写入文件失败:", err)
return
}
}
goroutine&waitgroup下载文件的更多相关文章
- Java下载文件(流的形式)
@RequestMapping("download") @ResponseBody public void download(HttpServletResponse respons ...
- 使用批处理文件在FTP服务器 上传下载文件
1.从ftp服务器根目录文件夹下的文件到指定的文件夹下 格式:ftp -s:[配置文件] [ftp地址] 如:ftp -s:c:\vc\ftpconfig.txt 192.168.1.1 建立一个 ...
- 通过form表单的形式下载文件。
在项目中遇到问题,要求动态拼接uri下载文件.但是由于项目的安全拦截导致window.location.href 和 window.open等新建窗口的方法都不行. 无意间百度到了通过form表单来下 ...
- SecureCRT上传和下载文件
SecureCRT上传和下载文件(下载默认目录) SecureCR 下的文件传输协议有ASCII .Xmodem .Ymodem .Zmodem ASCII:这是最快的传输协议,但只能传送文本文件. ...
- HTTP 错误 404.3 – Not Found 由于扩展配置问题而无法提供您请求的页面。如果该页面是脚本,请添加处理程序。如果应下载文件,请添加 MIME 映射。
今天,在vs2013中新建了一个placard.json文件,当我用jq读取它的时候,去提示404,直接在浏览器访问这个文件,提示: HTTP 错误 404.3 – Not Found 由于扩展配置问 ...
- FTP下载文件失败
这几天的定时任务下载文件的脚本失败了. 于是手工执行测试,发现报550 Permission denied. Passive mode refused. 意思就是被动模式下,没有权限获取文件. 解决方 ...
- 如何使用FileZilla上传和下载文件
一.使用FileZilla上传文件 1 打开 FileZilla 按照如下图所示,填写远程 Linux 的 IP ,用户名,密码,还有端口号(默认22) 2 选中左边需要上传的文件,然后拖到右边,等待 ...
- 开发板tftp下载文件
搭建过程: 1.安装相关软件包:tftpd(服务端),tftp(客户端),xinetd sudo apt-get install tftpd tftp xinetd 2.建立配置文件(蓝色的目录是可以 ...
- Linux上传下载文件快捷命令
远程链接Linux(如SecrueCRT),要上传文件很下载文件到Linux服务器,只需要使用sz或者rz命令即可快速下载和上传文件了. 使用方法: 1.首先确保Linux服务器系统中安装了lrzsz ...
随机推荐
- 基于UiAutomator2+PageObject模式开展APP自动化测试实战
前言 在上一篇<APP自动化测试框架-UiAutomator2基础>中,重点介绍了uiautomator2的项目组成.运行原理.环境搭建及元素定位等基础入门知识,本篇将介绍如何基于uiau ...
- JavaDoc文档生成详细操作
JavaDoc练习 JavaDoc是一种将注释生成HTML文档的技术,是用来生成自己API文档的. 参数信息 /* @author 作者名 @version 版本号 @since 知名最早需要使用的j ...
- Python之验证码识别功能
Python之pytesseract 识别验证码 1.验证码来一个 2.适合什么样的验证码呢? 只能识别简单.静态.无重叠.只有数字字母的验证码 3.实际应用:模拟人工登录.页面内容识别.爬虫抓取信息 ...
- pnpm凭什么这么快
前端包管理器层出不穷,pnpm算是一个后起之秀.它和npm有什么不同,为什么有了npm还要造一个pnpm? npm的问题 npm是最早的包管理器,安装nodejs自带npm,v3版本之前,npm安全依 ...
- Java Stream 函数式接口外部实例的引用
Java Function Interface 函数式接口: Stream.empty() .filter(Predicate) .map(Function) .forEach(Consumer); ...
- 使用puppeteer生成pdf与截图
之前写过一篇 vue cli2 使用 wkhtmltopdf 踩坑指南,由于wkhtmltopdf对vue的支持并不友好,而且不支持css3,经过调研最终选择puppeteer,坑少,比较靠谱. 一. ...
- 窗口部件-基础窗口部件 QWidget
1 基础窗口部件 QWidget QWidget 类是所有用户界面对象的基类,被称为基础窗口部件.不多废话直接看代码 main.cpp 如下 #include<QtWidgets> int ...
- Qt 场景创建
1 创建 Q t Widget Application 2 创建窗口 3 创建后的目录 创建完成后运行一下 4 导入资源 将res文件拷贝到 项目工程目录下 添加资源 选择一模版.Qt-Reso ...
- vivo 全球商城:电商平台通用取货码设计
vivo官网商城开发团队 - Zhou Longjian 一.背景 随着O2O线上线下业务的不断扩展,电商平台也在逐步完善交易侧相关的产品功能.在最近的需求版本中,业务方为进一步提升用户的使用体验,规 ...
- PHP之旅---出发(php+apache+MySQL)
@ 目录 前言 准备 php安装 Apache安装 MySQL安装 Navicat安装(附) Apache+php整合 验证Apache+php 前言 本文详细介绍php+apache+MySQL在w ...