当前位置:首页 > 文章列表 > Golang > Go问答 > Go HTTP Range 请求为什么返回 200 而不是 206

Go HTTP Range 请求为什么返回 200 而不是 206

来源:17golang原创 2026-10-23 02:56:59 0浏览 收藏

Go HTTP 请求带了 Range 却收到 200,最常见的原因是处理器只写出了完整内容,并没有实现范围响应。设置 Accept-Ranges: bytes 也不会自动把完整下载变成部分下载。另一个正常情况是 If-Range 与当前资源不匹配,服务端应回退为完整响应,避免客户端拼接两个版本。

官方地址:https://pkg.go.dev/net/http

先确认客户端实际发送的是 GET 和有效的 bytes 范围,再确认处理器是否使用 ServeContent 等范围感知实现。不要只把状态码改成 206;响应体和 Content-Range 必须对应同一段内容。

断点续传需要的不只是一个请求头

用户点击“继续下载”时,客户端通常已经保存一段内容,希望只取剩余字节。服务器收到 Range 是收到一个范围请求,不是必须返回 206 的承诺。HTTP 允许忽略范围请求,因此客户端要能够处理 200。对不支持 Range 的自定义接口,完整返回可能符合协议,但无法提供预期的续传体验。

对下载组件而言,最危险的不是显示“重新下载”,而是把 200 的完整响应直接追加到旧文件末尾。这样界面可能显示进度增加,最终文件却已损坏。所以排错要同时看状态码、范围字段、资源版本和实际字节,不能把“HTTP 请求成功”当成“可以续传”。

先把请求与响应字段对应起来

本例使用固定的 16 字节 ASCII 资源。请求 Range: bytes=0-3 表示从零开始、包含结束位置的四个字节。在单段范围被正常接受时,应得到 206、Content-Range: bytes 0-3/16,正文为 0123。Content-Length 若存在,表示本次响应内容长度,不是完整资源长度。

Range 定义在 GET 上;不要使用 curl -I 发送 HEAD 来代替真实范围下载观察。还要确认最终地址:登录跳转、对象下载重定向、反向代理可能使实际处理请求的服务不是你正在修改的 Go handler。

Range 请求字段与部分响应材料的静态关系
图1:范围响应结构图。请求条件和响应材料必须匹配,图示不是实际抓包记录。

If-Range 表示“版本没变就给这一段,变了就给完整内容”。使用实体标签时应使用强 ETag,不能把弱标签当成可拼接文件的保证。资源更新后返回 200 并不一定是故障;客户端需要丢弃旧的续传计划并重新获取当前完整版本。

用 ServeContent 实现范围感知的下载接口

下面是本次原创的完整演示程序,保存为 main.go 即可供读者运行。它同时提供只写完整正文的反例 /plain 和支持范围的 /ranged。本文没有启动服务或执行这些请求;下文结果均为对固定示例的预期说明。

package main

import (
    "errors"
    "io"
    "log"
    "net/http"
    "strings"
    "time"
)

// 固定资源及其强 ETag 必须始终一一对应;更改资源也要更改 ETag。
const payload = "0123456789abcdef"
const resourceTag = `"range-demo-v1"`

func allowRead(w http.ResponseWriter, r *http.Request) bool {
    if r.Method == http.MethodGet || r.Method == http.MethodHead {
        return true
    }
    w.Header().Set("Allow", "GET, HEAD")
    http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
    return false
}

func plain(w http.ResponseWriter, r *http.Request) {
    if !allowRead(w, r) { return }
    // 反例:声明支持 bytes 不等于实现了范围处理。
    w.Header().Set("Accept-Ranges", "bytes")
    w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    if r.Method == http.MethodHead { return }
    if _, err := io.WriteString(w, payload); err != nil {
        log.Printf("write response: %v", err)
    }
}

func ranged(w http.ResponseWriter, r *http.Request) {
    if !allowRead(w, r) { return }
    w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    w.Header().Set("ETag", resourceTag)
    // 每次请求独立游标;交给 ServeContent 判断条件、范围和状态码。
    // 零修改时间不生成 Last-Modified,此例只使用强 ETag。
    http.ServeContent(w, r, "sample.txt", time.Time{}, strings.NewReader(payload))
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/plain", plain)
    mux.HandleFunc("/ranged", ranged)
    // 只监听本机,超时适用于这个极小演示,不代表大文件生产配置。
    server := &http.Server{
        Addr: "127.0.0.1:8087", Handler: mux,
        ReadHeaderTimeout: 5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout: 30 * time.Second,
    }
    if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
        log.Fatal(err)
    }
}

ServeContent 接收可读且可定位的 ReadSeeker。strings.NewReader 适合这个固定小资源,每个请求创建独立游标;真实大文件可使用按请求打开的文件,并由调用方关闭文件。不要把普通远端流强行当作可定位内容,也不要把大文件全部读入内存只为适配示例。

调用 ServeContent 前不要先 WriteHeader(200),也不要先写提示文字:响应一旦提交,后续不能再正常选择 206 或其他状态。ETag 也应在调用前设置。本例 ETag 只适用于完全固定的 payload;换成可变文件后,必须确保标签随表示内容变化,不能继续沿用示例常量。

下载处理器与可定位资源的静态依赖结构
图2:处理器结构图。ServeContent 使用请求条件和独立 Reader 组织响应,不代表代码已运行。

下载交互应区分继续、重下和失败

先在一个终端运行服务,再在另一个终端执行请求。以下命令只供读者手动操作,不是文章制作过程中已经运行的检查。

# 在保存 main.go 的目录启动本机示例
go run main.go
# 查看反例:虽然声明 Accept-Ranges,仍完整返回
curl -i -H 'Range: bytes=0-3' http://127.0.0.1:8087/plain
# 正常单段请求:预期 206,正文四个字节
curl -i -H 'Range: bytes=0-3' http://127.0.0.1:8087/ranged
# 强 ETag 匹配:预期保留范围请求
curl -i -H 'Range: bytes=0-3' -H 'If-Range: "range-demo-v1"' http://127.0.0.1:8087/ranged
# 旧版本标签不匹配:预期 200 和完整十六字节正文
curl -i -H 'Range: bytes=0-3' -H 'If-Range: "old-demo"' http://127.0.0.1:8087/ranged
# 超出非空资源的单段范围:预期 416
curl -i -H 'Range: bytes=100-' http://127.0.0.1:8087/ranged
响应下载组件应做什么
206,单段 Content-Range 符合请求确认版本和偏移后接收该段,不盲目按当前文件末尾追加
200作为完整响应处理,不追加到旧版本残片
416重新判断远端长度和本地偏移,不等同于文件已完整
304 或 412按缓存或前置条件语义处理,不当作下载正文

界面上应分别给出“继续下载”“资源已更新,重新下载”和可重试错误,不要统一显示“续传成功”。进度提示使用文字同时说明状态,不能只靠颜色;键盘可操作的重试或重新下载控件,也比遇到 200 就循环重试更容易理解。

直连正常但经过代理仍返回 200 时看哪里

在自己的受控环境中,分别记录直连源站与正式链路收到的状态和关键头,比较 Range、If-Range、Content-Encoding、Content-Range 是否在同一表示上保持一致。只看到源站日志有 Range,还不足以说明客户端拿到的内容就是源站那一段。

动态 gzip 等内容变换需要特别谨慎:字节偏移对应选中的表示,不应先对原始内容切片,再随意压缩并沿用原范围头。示例故意不加压缩中间件。实际部署应让压缩、缓存和范围处理采用明确一致的方案,而不是手工改几个头“凑成 206”。同时评估大文件超时与并发开销;示例的十秒写超时只用于极小资源,不是生产下载配置建议。

只设置 Accept-Ranges 能解决吗?

不能。它用于声明范围能力,实际读取哪段、返回什么状态和字段仍由实现负责。

为什么没有 If-Range 仍然是 200?

优先确认 handler 没有自己完整写出正文、没有提前提交 200,以及请求是否真的到达支持范围的资源入口。然后再看代理与表示变换。

多个范围可以按普通文件片段直接追加吗?

不能。多段响应可能采用 multipart/byteranges,各段有自己的边界与范围信息;本文只演示单段,客户端不能把 multipart 包装当成文件字节。

参考:Go ServeContent 文档;HTTP Range 语义;If-Range 条件。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go netip 和 net.ParseIP 返回类型有什么差异Go netip 和 net.ParseIP 返回类型有什么差异
上一篇
Go netip 和 net.ParseIP 返回类型有什么差异
Go HTTP Cookie 的 Path 和 Domain 怎样影响发送范围
下一篇
Go HTTP Cookie 的 Path 和 Domain 怎样影响发送范围
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    599次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    640次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    672次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    638次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    458次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码