
本教程详细阐述如何使用 go 语言实现支持断点续传的大文件下载功能。文章深入解析 http/1.1 协议中的 range 请求机制,指导读者如何通过操作 http 头、获取本地文件大小以及以追加模式写入数据来构建一个高效且健壮的下载程序,确保下载过程的可恢复性。
在网络环境复杂多变的情况下,下载大文件时常常面临中断的风险。为了提高下载的可靠性和用户体验,支持断点续传(Resume Support)功能显得尤为重要。断点续传允许在下载中断后,从上次停止的位置继续下载,而非从头开始。本文将详细介绍如何使用 Go 语言实现这一功能,核心在于利用 HTTP/1.1 协议中的 Range 请求头。
HTTP/1.1 协议通过 Range 请求头和 Content-Range 响应头来支持部分内容请求,即断点续传。
客户端请求 (Range 头): 当客户端需要下载文件的某个片段时,会在 HTTP 请求头中添加 Range 字段,指定请求的字节范围。例如:
在断点续传场景中,我们通常使用 Range: bytes=start- 格式,其中 start 是本地已下载文件的大小。
服务器响应 (Content-Range 和状态码): 如果服务器支持 Range 请求,它会返回状态码 206 Partial Content,并在响应头中包含 Content-Range 字段,指示本次响应包含的数据范围以及文件总大小。例如:
此外,服务器通常会在响应头中包含 Accept-Ranges: bytes 来表明其支持按字节范围请求。
立即学习“go语言免费学习笔记(深入)”;
使用 Go 语言实现断点续传下载,主要涉及以下几个步骤:
在开始下载之前,需要检查本地是否存在同名文件。如果存在,则获取其当前大小,这将作为续传的起始点。
import (
"os"
)
// getDownloadedFileSize 获取本地文件大小,如果文件不存在则返回 0
func getDownloadedFileSize(filepath string) (int64, error) {
fileInfo, err := os.Stat(filepath)
if os.IsNotExist(err) {
return 0, nil // 文件不存在,从头开始下载
}
if err != nil {
return 0, fmt.Errorf("无法获取文件信息: %w", err)
}
return fileInfo.Size(), nil // 返回文件当前大小
}根据续传点(已下载文件大小),构建带有 Range 请求头的 HTTP GET 请求。
import (
"fmt"
"net/http"
)
// createResumeRequest 创建一个带有 Range 头的 HTTP 请求
func createResumeRequest(url string, startByte int64) (*http.Request, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("创建请求失败: %w", err)
}
if startByte > 0 {
req.Header.Add("Range", fmt.Sprintf("bytes=%d-", startByte))
}
return req, nil
}发送请求后,需要处理服务器的响应。根据状态码(200 或 206),决定如何处理数据流。如果服务器返回 206,则以追加模式打开本地文件,并将响应体数据写入文件。
import (
"io"
"log"
)
// downloadFileWithResume 支持断点续传的文件下载函数
func downloadFileWithResume(url, filepath string) error {
downloadedSize, err := getDownloadedFileSize(filepath)
if err != nil {
return err
}
req, err := createResumeRequest(url, downloadedSize)
if err != nil {
return err
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("发送 HTTP 请求失败: %w", err)
}
defer resp.Body.Close()
// 根据响应状态码处理
switch resp.StatusCode {
case http.StatusOK: // 200 OK,服务器忽略 Range 头,发送整个文件
log.Printf("服务器返回 200 OK,重新下载整个文件: %s", url)
// 如果本地已有文件,需要清空或覆盖
file, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return fmt.Errorf("打开文件失败: %w", err)
}
defer file.Close()
_, err = io.Copy(file, resp.Body)
if err != nil {
return fmt.Errorf("写入文件失败: %w", err)
}
log.Printf("文件下载完成: %s", filepath)
return nil
case http.StatusPartialContent: // 206 Partial Content,服务器支持 Range 请求
log.Printf("服务器返回 206 Partial Content,从字节 %d 处续传: %s", downloadedSize, url)
file, err := os.OpenFile(filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("打开文件失败: %w", err)
}
defer file.Close()
// 使用 io.Copy 将响应体数据流式写入文件
written, err := io.Copy(file, resp.Body)
if err != nil {
return fmt.Errorf("写入文件失败: %w", err)
}
log.Printf("已写入 %d 字节,文件下载完成: %s", written, filepath)
return nil
default:
return fmt.Errorf("非预期的 HTTP 状态码: %d %s", resp.StatusCode, resp.Status)
}
}下面是一个完整的 Go 语言示例,演示如何实现支持断点续传的文件下载。
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
)
// getDownloadedFileSize 获取本地文件大小,如果文件不存在则返回 0
func getDownloadedFileSize(filepath string) (int64, error) {
fileInfo, err := os.Stat(filepath)
if os.IsNotExist(err) {
return 0, nil // 文件不存在,从头开始下载
}
if err != nil {
return 0, fmt.Errorf("无法获取文件信息: %w", err)
}
return fileInfo.Size(), nil // 返回文件当前大小
}
// createResumeRequest 创建一个带有 Range 头的 HTTP 请求
func createResumeRequest(url string, startByte int64) (*http.Request, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("创建请求失败: %w", err)
}
if startByte > 0 {
req.Header.Add("Range", fmt.Sprintf("bytes=%d-", startByte))
}
return req, nil
}
// downloadFileWithResume 支持断点续传的文件下载函数
func downloadFileWithResume(url, filepath string) error {
downloadedSize, err := getDownloadedFileSize(filepath)
if err != nil {
return err
}
req, err := createResumeRequest(url, downloadedSize)
if err != nil {
return err
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("发送 HTTP 请求失败: %w", err)
}
defer resp.Body.Close()
// 根据响应状态码处理
switch resp.StatusCode {
case http.StatusOK: // 200 OK,服务器忽略 Range 头,发送整个文件
log.Printf("服务器返回 200 OK,重新下载整个文件: %s", url)
// 如果本地已有文件,需要清空或覆盖
file, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return fmt.Errorf("打开文件失败: %w", err)
}
defer file.Close()
_, err = io.Copy(file, resp.Body)
if err != nil {
return fmt.Errorf("写入文件失败: %w", err)
}
log.Printf("文件下载完成: %s", filepath)
return nil
case http.StatusPartialContent: // 206 Partial Content,服务器支持 Range 请求
log.Printf("服务器返回 206 Partial Content,从字节 %d 处续传: %s", downloadedSize, url)
file, err := os.OpenFile(filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("打开文件失败: %w", err)
}
defer file.Close()
// 使用 io.Copy 将响应体数据流式写入文件
written, err := io.Copy(file, resp.Body)
if err != nil {
return fmt.Errorf("写入文件失败: %w", err)
}
log.Printf("已写入 %d 字节,文件下载完成: %s", written, filepath)
return nil
case http.StatusRequestedRangeNotSatisfiable: // 416 Range Not Satisfiable
// 这通常意味着请求的 Range 超出了文件大小,可能是本地文件已完整
log.Printf("服务器返回 416 Range Not Satisfiable,可能文件已完整或请求范围有误: %s", url)
// 可以再次检查本地文件大小和服务器返回的文件大小是否一致
return nil
default:
return fmt.Errorf("非预期的 HTTP 状态码: %d %s", resp.StatusCode, resp.Status)
}
}
func main() {
// 替换为你要下载的实际文件 URL
// 建议使用一个支持 Range 请求的公共测试文件,例如:
// "http://speedtest.tele2.net/1MB.zip" 或 "http://ipv4.download.thinkbroadband.com/10MB.zip"
fileURL := "http://ipv4.download.thinkbroadband.com/10MB.zip"
fileName := "10MB.zip"
downloadPath := filepath.Join(".", fileName) // 下载到当前目录
log.Printf("开始下载文件: %s 到 %s", fileURL, downloadPath)
err := downloadFileWithResume(fileURL, downloadPath)
if err != nil {
log.Fatalf("文件下载失败: %v", err)
}
log.Println("文件下载过程结束。")
// 模拟中断后再次尝试下载
// 可以手动删除部分文件或中断程序,然后再次运行
// log.Println("模拟中断后再次尝试下载...")
// err = downloadFileWithResume(fileURL, downloadPath)
// if err != nil {
// log.Fatalf("文件续传失败: %v", err)
// }
// log.Println("文件续传过程结束。")
}通过本文的详细介绍和示例代码,读者应该能够理解并实现基于 Go 语言的 HTTP 断点续传文件下载功能。核心在于巧妙利用 HTTP/1.1 协议的 Range 请求头,结合 Go 语言强大的网络和文件 IO 能力,构建出高效、可靠的下载程序。掌握这一技术,对于开发需要处理大文件传输的网络应用具有重要意义。
以上就是GoLang 实现 HTTP 断点续传下载教程的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号