在 Golang 中实现文件的断点续传主要涉及两个关键步骤:一是客户端能够记录已传输的文件部分,二是在服务器端能够识别并处理这些部分请求。以下是一种基本的实现方式,使用 HTTP 的Range
头和Content-Range
响应头来支持断点续传。
客户端实现
客户端在首次请求文件时,会从文件的开始位置请求数据。如果传输中断,下次请求时,客户端应发送一个包含Range
头的 HTTP 请求,指明从上次断开的位置继续传输。
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func downloadFile(url string, filePath string, start int64) error {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}
req.Header.Set("Range", fmt.Sprintf("bytes=%d-", start))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusPartialContent {
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, resp.Body)
return err
}
func main() {
url := "http://example.com/largefile.zip"
filePath := "./largefile.zip"
start := int64(0)
for {
err := downloadFile(url, filePath, start)
if err != nil {
fmt.Println("Error:", err)
break
}
break // 如果没有错误,说明文件已经完整下载
}
}
服务器端实现
服务器端需要检查Range
头,并相应地读取文件的一部分,然后设置正确的Content-Range
头来指示响应的范围。
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
)
func serveFile(w http.ResponseWriter, r *http.Request) {
fileName := "largefile.zip"
file, err := os.Open(fileName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
fileInfo, _ := file.Stat()
fileSize := fileInfo.Size()
rangeHeader := r.Header.Get("Range")
if rangeHeader == "" {
http.ServeFile(w, r, fileName)
return
}
var start int64
var end int64 = fileSize - 1
rangeParts := bytes.Split([]byte(rangeHeader), []byte("="))
if len(rangeParts) > 1 {
rangeBytes := bytes.Split(rangeParts[1], []byte("-"))
if len(rangeBytes) >= 1 {
start, _ = strconv.ParseInt(string(rangeBytes[0]), 10, 64)
}
if len(rangeBytes) == 2 {
end, _ = strconv.ParseInt(string(rangeBytes[1]), 10, 64)
}
}
if start < 0 || start > fileSize || end < 0 || end > fileSize {
w.WriteHeader(http.StatusRequestRangeNotSatisfiable)
return
}
file.Seek(start, 0)
w.Header().Set("Accept-Ranges", "bytes")
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)
io.CopyN(w, file, end-start+1)
}
func main() {
http.HandleFunc("/", serveFile)
http.ListenAndServe(":8080", nil)
}
在这个示例中,客户端和服务器都使用了标准库中的函数来处理 HTTP 请求和响应。客户端使用Range
头来请求文件的特定部分,而服务器则检查这个头并使用Content-Range
头来响应正确的文件部分。这种机制允许在传输中断后恢复下载,从而实现了断点续传。