從 .NET 轉來 GO 後,很多 API 的用法都要重新學習,今天就來熟悉如何使用 GO 來建立下載檔案的 Response。

設定 Header

以檔案下載的來說,我們有 3 個重要的 header 要設定。
Content-Type
這是用來告訴瀏覽器,回傳的檔案是什麼。 例如,是 jpeg 圖片的話,要設為 Content-Type: image/jpeg。 而 Go 有 http.DetectContentType 可以用來判斷回傳的檔案是哪一種 Content-Type 。

這不一定要設定,使用 http.ServeContenthttp.ServeFile 的話,會自動幫你設定好。

Content-Disposition
我們要用這個 Header 來告訴瀏覽器,我回傳的是一個檔案,例如 Content-Disposition: attachment; filename="filename.pdf

以 Chrome 來說,當 Content-Disposition: attachment 設定之後,瀏覽器才會執行下載,否則他會在新的 tab 開啟檔案。

Content-Length
這個 Header 是告訴瀏覽器檔案的大小,例如 content-length: 54138

這不一定要設定,使用 http.ServeContenthttp.ServeFile 的話,會自動幫你設定好。

讀取檔案

讀取檔案的話我們要用的是 ioutil.ReadFile,讀取完檔案後,此 API 會回傳 byte[]。

傳送 Response

我們要使用到的是 http.ServeContent 這個 API,這會將我們設定好的 Header 以及 byte[] 的資料寫入 Response 並回應給瀏覽器。 http.ServeContent 適合用在當你只能拿到 byte[] 的資料時候使用,如果你能夠明確指定檔案路徑的話,可以使用 http.ServeFile

請參考下方的範例程式碼,當使用者存取 http://localhost:8000/download 的時候,就會下載 test.pdf 的檔案了。

http.ServeContent
  • go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"time"
)

func main() {
http.HandleFunc("/download", downloadFile)
fmt.Println("Server run on port 8000")
http.ListenAndServe(":8000", nil)
}

func downloadFile(w http.ResponseWriter, r *http.Request) {
file := "test.pdf"

// 讀取檔案
downloadBytes, err := ioutil.ReadFile(file)

if err != nil {
fmt.Println(err)
}

// 取得檔案的 MIME type
mime := http.DetectContentType(downloadBytes)

fileSize := len(string(downloadBytes))

w.Header().Set("Content-Type", mime)
w.Header().Set("Content-Disposition", "attachment; filename="+file)
w.Header().Set("Content-Length", strconv.Itoa(fileSize))

http.ServeContent(w, r, file, time.Now(), bytes.NewReader(downloadBytes))
}

另一種回應檔案的方式

Go 還有提供另一個 http.ServeFile API,只要傳入檔案路徑,就會幫我們回傳檔案了,這個 API 比 http.ServeContent 簡單許多。

http.ServeFile
  • go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"net/http"
)

func main() {
http.HandleFunc("/download", downloadFile)
fmt.Println("Server run on port 8000")
http.ListenAndServe(":8000", nil)
}

func downloadFile(w http.ResponseWriter, r *http.Request) {
file := "test.pdf"

// 設定此 Header 告訴瀏覽器下載檔案。 如果沒設定則會在新的 tab 開啟檔案。
// w.Header().Set("Content-Disposition", "attachment; filename="+file)

http.ServeFile(w, r, file)
}

延伸閱讀

[HTTP File Upload and Download with Go]
[Golang : Force download file example]
[Downloading large files in Go]