7

使用 C#/PowerShell 實現檔案下載續傳功能

 3 years ago
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.
neoserver,ios ssh client
使用 C#/PowerShell 實現檔案下載續傳功能-黑暗執行緒

從網站下載大檔案,若下載一半中斷,從中斷處繼續下載已是所有瀏覽器的基本功能。其背後原理是透過 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 程式實現下載中斷續傳。

對自己造輪子沒興趣的朋友,以下是一些支援續傳下傳的現成解決方案:

讀到這裡的同學,歡迎加入造輪子的行列。

為求省事,我用 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 續傳下載功能了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK