商城首页欢迎来到中国正版软件门户

您的位置:首页 >如何在 Go 中实现文件下载的暂停与续传功能

如何在 Go 中实现文件下载的暂停与续传功能

  发布于2026-04-08 阅读(0)

扫一扫,手机访问

如何在 Go 中实现文件下载的暂停与续传功能

本文详解如何在 Go 语言中为 HTTP 文件下载添加真正的暂停(取消)与续传(Range 请求)能力,涵盖 http.Request.Cancel 的正确用法、断点续传的核心原理、Range 头设置、本地文件偏移写入及关键注意事项。

本文详解如何在 Go 语言中为 HTTP 文件下载添加真正的暂停(取消)与续传(Range 请求)能力,涵盖 `http.Request.Cancel` 的正确用法、断点续传的核心原理、Range 头设置、本地文件偏移写入及关键注意事项。

在 Go 中,“暂停下载”并非字面意义的线程挂起,而是主动取消当前请求;而“续传”本质上是发起新的 HTTP GET 请求,并通过 Range 请求头告知服务器从指定字节位置继续传输。这要求服务端支持 Accept-Ranges: bytes(绝大多数静态文件服务器默认支持),且客户端需维护已下载的字节数与本地文件状态。

✅ 实现步骤概览

  1. 首次下载:发送普通 GET 请求,边读边写入文件(记录总写入字节数);
  2. 暂停(取消):调用 cancelFunc() 关闭 context.WithCancel 创建的上下文;
  3. 续传准备:检查本地文件大小,作为 Range: bytes=<offset>- 的起始位置;
  4. 续传请求:设置 Range 头 + 使用 os.O_APPEND 或 os.SEEK_SET 定位写入位置;
  5. 校验一致性:建议比对 Content-Range 响应头与预期范围,避免中间状态错乱。

? 核心代码示例

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func downloadWithResume(url, filepath string, resume bool) error {
    var offset int64 = 0
    if resume {
        if fi, err := os.Stat(filepath); err == nil {
            offset = fi.Size()
        }
    }

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return err
    }

    // 设置 Range 头(仅续传时)
    if offset > 0 {
        req.Header.Set("Range", fmt.Sprintf("bytes=%d-", offset))
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return fmt.Errorf("request failed: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
        return fmt.Errorf("unexpected status: %s (%d)", resp.Status, resp.StatusCode)
    }

    // 确保以追加模式打开(或 seek 到 offset)
    f, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return err
    }
    defer f.Close()

    if offset > 0 {
        if _, err := f.Seek(offset, io.SeekStart); err != nil {
            return fmt.Errorf("seek failed: %w", err)
        }
    }

    // 复制数据
    n, err := io.Copy(f, resp.Body)
    if err != nil && err != io.EOF {
        return fmt.Errorf("copy failed: %w", err)
    }

    fmt.Printf("Downloaded %d additional bytes\n", n)
    return nil
}

// 使用示例
func main() {
    url := "https://httpbin.org/bytes/1000000"
    file := "download.bin"

    // 首次下载(不 resume)
    fmt.Println("Starting initial download...")
    if err := downloadWithResume(url, file, false); err != nil {
        panic(err)
    }

    // 模拟暂停后续传(例如用户点击“继续”)
    fmt.Println("Resuming download...")
    if err := downloadWithResume(url, file, true); err != nil {
        panic(err)
    }
}

⚠️ 关键注意事项

  • 服务端必须支持 Range:可通过 HEAD 请求检查响应头是否含 Accept-Ranges: bytes;
  • 不要混用 O_APPEND 和 Seek:O_APPEND 会忽略 Seek,续传务必使用 O_WRONLY + Seek;
  • 响应头验证:成功续传时,Content-Range 应类似 bytes 12345-999999/1000000,需校验起始值是否等于 offset;
  • 并发安全:若支持多任务下载/暂停,请用 sync.Mutex 保护文件句柄与状态变量;
  • 取消 ≠ 立即停止 I/O:context.Cancel 触发后,http.Transport 会尽快中断连接,但底层 TCP 可能仍有少量缓冲数据。

✅ 总结

Go 中的“暂停/续传”是基于 HTTP 协议语义的协作式机制,而非操作系统级挂起。掌握 context 取消、Range 头构造、文件偏移写入三者联动,即可构建健壮的断点续传下载器。建议进一步封装为结构体(如 Downloader),支持进度回调、错误重试和校验和验证,以满足生产环境需求。

本文转载于:互联网 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注