2 minutes
实现服务端断点续传:Go与Nginx
一、HTTP协议基础
HTTP协议通过Range请求实现断点续传:
客户端请求指定范围
客户端在请求头中携带Range
字段,例如:GET /file.zip HTTP/1.1 Range: bytes=500-1000
服务端响应部分内容
若支持范围请求,服务端返回状态码206 Partial Content
及对应数据片段:HTTP/1.1 206 Partial Content Content-Range: bytes 500-1000/5000 Content-Length: 501
完整性校验机制
通过ETag
或Last-Modified
头确保文件未变更,避免续传数据不一致。
二、Nginx静态资源断点续传
Nginx默认支持静态文件的断点续传。需要有以下配置:
server {
location /static {
root /data/files; # 文件存储路径
add_header Accept-Ranges bytes; # 声明支持字节范围请求
}
}
验证方法:
使用curl
检测响应头:
curl -I http://your-domain/static/large-file.iso
若输出包含Accept-Ranges: bytes
与Content-Length
,则表明支持续传。
三、Go实现
对于动态生成的文件(如需鉴权的资源),需手动处理Range
请求。
package main
import (
"fmt"
"net/http"
"os"
"strconv"
"strings"
)
func handleDownload(w http.ResponseWriter, r *http.Request) {
filePath := "/data/dynamic-file.bin"
file, err := os.Open(filePath)
if err != nil {
http.Error(w, "File not found", http.StatusNotFound)
return
}
defer file.Close()
fileInfo, _ := file.Stat()
fileSize := fileInfo.Size()
w.Header().Set("Content-Length", strconv.FormatInt(fileSize, 10))
w.Header().Set("ETag", fmt.Sprintf("\"%x\"", fileInfo.ModTime().UnixNano()))
rangeHeader := r.Header.Get("Range")
if rangeHeader == "" {
http.ServeContent(w, r, fileInfo.Name(), fileInfo.ModTime(), file)
return
}
ranges := strings.Split(rangeHeader, "=")[1]
parts := strings.Split(ranges, "-")
start, _ := strconv.ParseInt(parts[0], 10, 64)
end := fileSize - 1
if parts[1] != "" {
end, _ = strconv.ParseInt(parts[1], 10, 64)
}
if start >= fileSize || end >= fileSize {
http.Error(w, "Requested range not satisfiable", http.StatusRequestedRangeNotSatisfiable)
return
}
w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, fileSize))
w.Header().Set("Content-Length", strconv.FormatInt(end-start+1, 10))
w.WriteHeader(http.StatusPartialContent)
file.Seek(start, 0)
http.ServeContent(w, r, fileInfo.Name(), fileInfo.ModTime(), file)
}
func main() {
http.HandleFunc("/download", handleDownload)
http.ListenAndServe(":8080", nil)
}
• 解析Range
请求头并验证范围有效性
• 使用Seek
定位文件指针,返回部分内容
• 通过ETag
实现文件一致性校验
四、客户端如何检测服务端是否支持?
可通过以下步骤判断:
发送HEAD请求
获取响应头信息:curl -I http://your-domain/file.zip
检查关键头字段
•Accept-Ranges: bytes
:表明支持字节范围请求 •Content-Length
:必须存在且为固定值(动态内容可能无法支持) •ETag
或Last-Modified
:用于文件变更校验实验性范围请求测试
发送带Range
头的GET请求:curl -H "Range: bytes=0-100" http://your-domain/file.zip
若响应状态码为
206
且包含Content-Range
头,则确认支持续传。
五、Nginx反向代理Go服务的注意事项
当Go服务部署于Nginx后,需确保配置正确处理Range请求:
location /go-download {
proxy_pass http://go-backend:8080/download;
proxy_set_header Range $http_range; # 传递原始Range头
proxy_set_header If-Range $http_if_range;
proxy_hide_header Accept-Ranges; # 避免与后端冲突
proxy_http_version 1.1; # 支持HTTP/1.1特性
}
• 确认Nginx与Go服务对文件有读取权限
• 检查Content-Length
是否被意外修改(如Gzip压缩)
• 使用tcpdump
或Wireshark抓包验证请求头传递
六、边界问题与优化建议
多范围请求处理
支持形如Range: bytes=0-100,200-300
的请求需分段响应,可通过Go的multipart/byteranges
实现。速率限制与防滥用
Nginx配置限速:location /download { limit_rate 1m; # 限制下载速度为1MB/s }
日志监控
监控206
状态码频率,识别异常续传行为。