使用 C#/PowerShell 實現檔案下載續傳功能
source link: https://blog.darkthread.net/blog/csharp-resume-donwload/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
從網站下載大檔案,若下載一半中斷,從中斷處繼續下載已是所有瀏覽器的基本功能。其背後原理是透過 HTTP 1.1 協定加入的 HTTP Accept-Ranges 及 Range 規格,網站透過 Response Header Accept-Ranges: byte 表明自己接受分段下載;客戶端發送 GET Request 時加入 Header Range: bytes=2048-150000 指定下載範圍,從第幾個 Byte 下載到第幾個 Byte。主流網站如 IIS、Apache 早已支援 Range 續傳功能,而 .NET 從 1.1 時代也提供 HttpWebRequest.AddRange()方法實現續傳。看到這裡,想必大家知道我要做什麼了,是的,我想試試自己寫 C#/PowerShell 程式實現下載中斷續傳。
對自己造輪子沒興趣的朋友,以下是一些支援續傳下傳的現成解決方案:
- 幾乎所有瀏覽器都支援續傳下載,不想寫程式找工具,開瀏覽器下載就好了。
- Linux 上有強大的 wget 下載工具支援續傳,Windows 平台有 GNU Win32 版 Wget 可用。
- PowerShell 7 內建 -Resume 參數直接處理續傳事宜(配合 -OutFile 參數使用)。
- 在 NuGet 有網友寫好的現成元件 - DownloadUtilities。
讀到這裡的同學,歡迎加入造輪子的行列。
為求省事,我用 PowerShell 寫,但核心部分是靠 C# HttpWebRequest,相同邏輯可輕易轉成 .NET 程式。不囉嗦,直接上程式碼:
$ErrorActionPreference = "STOP"
$url = "http://localhost/aspnet/music.mp3"
$file = "test.mp3"
if (!(Test-Path $file)) {
# 若檔案不存在,用單純 Invoke-WebRequest 或 WebClient.DownloadFile 就好
# (New-Object System.Net.WebClient).DownloadFile($url, $file)
Invoke-WebRequest -Uri $url -OutFile $file
}
else {
# .NET 工作目錄與 PowerShell 可能不用,取得完整路徑供 .NET 使用
$fileFullPath = (Resolve-Path $file).Path
# 若檔案存在,查現有檔案大小,使用 Range Header 續傳
# 取得現有檔案大小,由後面續傳
# PowerShell 7 Invoke-WebRequest 直接加 -Resume 即可
$currLength = (Get-Item $file).Length
# Invoke-WebRequest -Uri $url -Headers @{"Range"="bytes=$currLength-"} -OutFile "$file.resume"
# 以上寫法不 Work -> The 'RANGE' header must be modified using the appropriate property or method.
# 用 HttpWebRequest 實現
[System.Net.HttpWebRequest] $req = [System.Net.WebRequest]::Create($url)
$req.Method = "GET"
$req.AddRange($currLength)
try {
$resp = $req.GetResponse()
}
catch [System.Net.WebException] {
# 若檔案先前已下載完成,伺服器會由 Range 已到檔案結尾回傳 HTTP 416,此時不需續傳,直接結束
if ($_.Exception.Response.StatusCode -eq [System.Net.HttpStatusCode]::RequestedRangeNotSatisfiable) {
Write-Host "檔案已完成"
return
}
else {
$_.Exception
}
}
Write-Host "從 $currLength 開始續傳"
$respStream = $resp.GetResponseStream()
$contRange = $resp.Headers['Content-Range'] # ex: Content-Range: bytes 0-50/1270
if (!$contRange) { throw "無法續傳" }
$totalLen = $contRange.Split('/')[1]
$fileStream = New-Object System.IO.FileStream($fileFullPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Write, [System.IO.FileShare]::Read)
try {
$fileStream.Seek($currLength, [System.IO.SeekOrigin]::Begin) | Out-Null
# 以 8K 為單位從 Response Stream 讀取 byte[] 寫入 FileStream
[byte[]]$buff = [byte[]]::CreateInstance([byte], 8192)
do {
$bytesRead = $respStream.Read($buff, 0, $buff.Length)
$fileStream.Write($buff, 0, $bytesRead)
Write-Progress -Activity "續傳下載中" -Status "$($fileStream.Position)/$totalLen" -PercentComplete ($fileStream.Position * 100 / $totalLen)
} while ($bytesRead -gt 0)
$fileStream.Close()
}
finally {
$fileStream.Dispose()
}
$respStream.Close()
}
簡單測試,第一次下載到一半按 Ctrl-C 中斷,再次執行時會從上次中斷的地方繼續,最後我用 FC.exe 工具檢查,確認下載內容與原始檔案完全一致。
加碼測試多次續傳,也 OK。
就醬,我們現在也會用 .NET 寫 HTTP 續傳下載功能了。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK