Go言語:大容量ファイル転送と中断からの再開【実装ガイド】
ゴーラングでは、大容量ファイルの転送や一時停止した箇所からの継続転送を実現するためにHTTPプロトコルを使用することができます。以下は簡単なサンプルコードです。
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
)
func handler(w http.ResponseWriter, r *http.Request) {
file, err := os.Open("large_file.txt")
if err != nil {
fmt.Println("Error opening file: ", err)
return
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
fmt.Println("Error getting file info: ", err)
return
}
w.Header().Set("Content-Disposition", "attachment; filename=large_file.txt")
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10))
http.ServeContent(w, r, "large_file.txt", fileInfo.ModTime(), file)
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
この例では、最初にlarge_file.txtという大きなファイルを開き、HTTPレスポンスヘッダを設定し、その後、http.ServeContent関数を使用してファイルの内容をクライアントに送信しました。 クライアントはhttp://localhost:8080にアクセスしてこの大きなファイルをダウンロードすることができます。
断点续传功能を実現したい場合は、HTTPリクエストのRangeヘッダーをチェックして、ファイルのどの位置から転送を開始するかを決定することができます。以下は修正されたサンプルコードです:
func handler(w http.ResponseWriter, r *http.Request) {
file, err := os.Open("large_file.txt")
if err != nil {
fmt.Println("Error opening file: ", err)
return
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
fmt.Println("Error getting file info: ", err)
return
}
w.Header().Set("Content-Disposition", "attachment; filename=large_file.txt")
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10))
rangeHeader := r.Header.Get("Range")
if rangeHeader != "" {
startRange, endRange, err := parseRangeHeader(rangeHeader, fileInfo.Size())
if err != nil {
fmt.Println("Error parsing range header: ", err)
return
}
w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", startRange, endRange, fileInfo.Size()))
http.ServeContent(w, r, "large_file.txt", fileInfo.ModTime(), io.NewSectionReader(file, startRange, endRange-startRange+1))
} else {
http.ServeContent(w, r, "large_file.txt", fileInfo.ModTime(), file)
}
}
func parseRangeHeader(rangeHeader string, fileSize int64) (int64, int64, error) {
prefix := "bytes="
if len(rangeHeader) < len(prefix) || rangeHeader[:len(prefix)] != prefix {
return 0, 0, fmt.Errorf("Invalid range header format")
}
rangeStr := rangeHeader[len(prefix):]
dashIndex := strings.IndexByte(rangeStr, '-')
if dashIndex == -1 {
return 0, 0, fmt.Errorf("Invalid range header format")
}
startRange, err := strconv.ParseInt(rangeStr[:dashIndex], 10, 64)
if err != nil {
return 0, 0, err
}
endRangeStr := rangeStr[dashIndex+1:]
var endRange int64
if endRangeStr == "" {
endRange = fileSize - 1
} else {
endRange, err = strconv.ParseInt(endRangeStr, 10, 64)
if err != nil {
return 0, 0, err
}
}
return startRange, endRange, nil
}
修正されたサンプルコードでは、まずHTTPリクエストのRangeヘッダーを解析し、その後リクエストされた範囲に応じてファイル内容を読み取り、クライアントに転送される内容の範囲を設定するためにContent-Rangeヘッダーを設定しました。
この方法により、大きなファイルの転送や途中での続きから転送が可能となります。実際の応用では、必要に応じてコードをさらに最適化したり拡張したりすることができます。