使用 Go 語言開發(fā)一個并發(fā)文件下載器
本文轉(zhuǎn)載自微信公眾號「Golang來啦」,作者Seekload。轉(zhuǎn)載本文請聯(lián)系Golang來啦公眾號。
今天給大家分享一個實戰(zhàn)項目,涉及到的知識點還挺多,文末也有源碼地址!!
原文如下:
Go 語言是一門了不起的語言,盡管它非常簡單,與 Koltin 和 Scala 等其他現(xiàn)代語言相比,它的功能很少,但它具有強大的并發(fā)能力。這篇文章,我們將會看到使用 Go 語言如何編寫一個完整的并發(fā)文件下載器。完整的代碼在這里[1]。
檢查服務(wù)器是否支持并發(fā)下載
如何之前使用過類似 IDM 的下載工具,你可能會注意到它支持并發(fā)下載文件。
可以看到下載文件的時候啟動了 8 個進程。
實現(xiàn)并發(fā)下載,我們必須確保服務(wù)器支持范圍請求。怎么確認呢?我們可以發(fā)送 HEAD 請求,如果響應(yīng)頭的 Accept-Ranges 返回的值是 bytes,我們就能確定服務(wù)器支持此功能。
- res, err := http.Head("http://some.domain/some.file")
- if err != nil {
- log.Fatal(err)
- }
- if res.StatusCode == http.StatusOK && res.Header.Get("Accept-Ranges") == "bytes" {
- // Yeh, server supports partial request
- }
如何下載文件的其中一部分
設(shè)想服務(wù)器支持范圍請求,我們知道文件大小是 4000 字節(jié)(文件大小從響應(yīng)頭的 Content-Length 獲取)。要僅下載 2000 到 3000 字節(jié)的文件的一部分,我們可以發(fā)送 HTTP GET 請求,并在 header 頭設(shè)置 Range 參數(shù):
- curl -X GET -H "Range: bytes=2000-3000" -o OUTPUT_FILE http://some.domain/some.file
實現(xiàn)相同功能的代碼如下:
- req, err := http.NewRequest("GET", "http://some.domain/some.file", nil)
- if err != nil {
- log.Fatal(err)
- }
- rangeStart := 2000
- rangeStop := 3000
- req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", rangeStart, rangeStop))
- // make a request
- res, err := http.DefaultClient.Do(req)
將響應(yīng)保存在文件中
為了支持斷點續(xù)傳功能,我們不會將請求響應(yīng)保存在內(nèi)存里,而是會持久化在文件中。舉個例子,如果我們把并發(fā)級別設(shè)置成 4,在輸出目錄將會有 4 個臨時文件。下面的代碼,我們只是簡單地讀取 HTTP 響應(yīng)體并將它寫入一個文件中:
- f, err := os.OpenFile(outputPath, flags, 0644)
- if err != nil {
- log.Fatal(err)
- }
- defer f.Close()
- _, err = io.Copy(f, res.Body)
暫停下載
不知道大家注意到?jīng)]有,上面代碼有個問題,使用時不支持 CTRL+C 暫停下載。如果下載的文件過大,或者網(wǎng)絡(luò)慢,下載需要花費很長時間。因為 io.Copy 復(fù)制文件時遇到 EOF 或者發(fā)生錯誤才結(jié)束。為了解決這個問題,我們使用 io.CopyN 和 cancel channel 組合:
- // copy to output file
- for {
- select {
- case <- context.Done():
- // user canceled the download
- return
- default:
- _, err = io.CopyN(f, res.Body, BUFFER_SIZE))
- if err != nil {
- if err == io.EOF {
- return
- } else {
- log.Fatal(err)
- }
- }
- }
- }
其他功能參見完整源代碼
這篇文章只提到了代碼中最重要的部分,但是通過閱讀代碼你可以了解其他功能是怎么實現(xiàn)的,比如:進度條的工作方式、如何使用 sync 包實現(xiàn)部分下載的同步、如何合并臨時文件以及如何實現(xiàn)恢復(fù)功能等。所以可以通過閱讀倉庫代碼[2]獲取更多信息。
參考資料
[1]這里: https://github.com/mostafa-asg/go-dl
[2]倉庫代碼: https://github.com/mostafa-asg/go-dl
via:
https://returnfn.com/lets-build-a-concurrent-file-downloader-in-go
作者:Mostafa Asgari