使用 Go 語言完成 HTTP 文件上傳與下載

語言: CN / TW / HK

爭做團隊核心程序員,關注「 幽鬼

近我使用 Go 語言完成了一個正式的 Web 應用,有一些方面的問題在使用 Go 開發 Web 應用過程中比較重要。過去,我將 Web 開發作為一項職業並且把使用不同的語言和範式開發 Web 應用作為一項愛好,因此對於 Web 開發領域有一些心得體會。

總的來説,我喜歡使用 Go 語言進行 Web 開發,儘管開始一段時間需要去適應它。Go 語言有一些坑,但是正如本篇文章中所要討論的文件上傳與下載,Go 語言的標準庫與內置函數,使得開發是種愉快的體驗。

在接下來的幾篇文章中,我將重點討論我在 Go 中編寫生產級 Web 應用程序時遇到的一些問題,特別是關於身份驗證/授權的問題。

這篇文章將展示 HTTP 文件上傳和下載的基本示例。我們將一個有 type 文本框和一個 uploadFile 上傳框的 HTML 表單作為客户端。

讓我們來看下 Go 語言中是如何解決這種在 Web 開發中隨處可見的問題的。

代碼示例

首先,我們在服務器端設定兩個路由, /upload 用於文件上傳, /files/* 用於文件下載。

const maxUploadSize = 2 * 1024 * 2014 // 2 MB
const uploadPath = "./tmp"

func main() {
 http.HandleFunc("/upload", uploadFileHandler())

 fs := http.FileServer(http.Dir(uploadPath))
 http.Handle("/files/", http.StripPrefix("/files", fs))

 log.Print("Server started on localhost:8080, use /upload for uploading files and /files/{fileName} for downloading files.")
 log.Fatal(http.ListenAndServe(":8080", nil))
}

我們還將要上傳的目標目錄,以及我們接受的最大文件大小定義為常量。注意這裏,整個文件服務的概念是如此的簡單 —— 我們僅使用標準庫中的工具,使用 http.FileServe 創建一個 HTTP 處理程序,它將使用 http.Dir(uploadPath) 提供的目錄來上傳文件。

現在我們只需要實現 uploadFileHandler

這個處理程序將包含以下功能:

  • 驗證文件最大值

  • 從請求驗證文件和 POST 參數

  • 檢查所提供的文件類型(我們只接受圖像和 PDF)

  • 創建一個隨機文件名

  • 將文件寫入硬盤

  • 處理所有錯誤,如果一切順利返回成功消息

第一步,我們定義處理程序:

func uploadFileHandler() http.HandlerFunc {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

然後,我們使用 http.MaxBytesReader 驗證文件大小,當文件大小大於設定值時它將返回一個錯誤。錯誤將被一個助手程序 renderError 進行處理,它返回錯誤信息及對應的 HTTP 狀態碼。

 r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
 if err := r.ParseMultipartForm(maxUploadSize); err != nil {
  renderError(w, "FILE_TOO_BIG", http.StatusBadRequest)
  return
 }

如果文件大小驗證通過,我們將檢查並解析表單參數類型和上傳的文件,並讀取文件。在本例中,為了清晰起見,我們不使用花哨的 io.Readerio.Writer 接口,我們只是簡單的將文件讀取到一個字節數組中,這點我們後面會寫到。

 fileType := r.PostFormValue("type")
 file, _, err := r.FormFile("uploadFile")
 if err != nil {
  renderError(w, "INVALID_FILE", http.StatusBadRequest)
  return
 }
 defer file.Close()
 fileBytes, err := ioutil.ReadAll(file)
 if err != nil {
  renderError(w, "INVALID_FILE", http.StatusBadRequest)
  return
 }

現在我們成功的驗證了文件的大小,並且讀取了文件,接下來我們該檢驗文件的類型了。一種廉價但是並不安全的方式,只檢查文件擴展名,並相信用户沒有改變它,但是對於一個正式的項目來講不應該這麼做。

幸運的是,Go 標準庫提供給我們一個 http.DetectContentType 函數,這個函數基於 mimesniff 算法,只需要讀取文件的前 512 個字節就能夠判定文件類型。

 filetype := http.DetectContentType(fileBytes)
 if filetype != "image/jpeg" && filetype != "image/jpg" &&
  filetype != "image/gif" && filetype != "image/png" &&
  filetype != "application/pdf" {
  renderError(w, "INVALID_FILE_TYPE", http.StatusBadRequest)
  return
 }

在實際應用程序中,我們可能會使用文件元數據做一些事情,例如將其保存到數據庫或將其推送到外部服務——以任何方式,我們將解析和操作元數據。這裏我們創建一個隨機的新名字(這在實踐中可能是一個 UUID)並將新文件名記錄下來。

 fileName := randToken(12)
 fileEndings, err := mime.ExtensionsByType(fileType)
 if err != nil {
  renderError(w, "CANT_READ_FILE_TYPE", http.StatusInternalServerError)
  return
 }
 newPath := filepath.Join(uploadPath, fileName+fileEndings[0])
 fmt.Printf("FileType: %s, File: %s\n", fileType, newPath)

馬上就大功告成了,只剩下一個關鍵步驟-寫文件。如上文所提到的,我們只需要複製讀取的二進制文件到一個新創建的名為 newFile 的文件處理程序裏。

如果所有部分都沒問題,我們給用户返回一個 SUCCESS 信息。

 newFile, err := os.Create(newPath)
 if err != nil {
  renderError(w, "CANT_WRITE_FILE", http.StatusInternalServerError)
  return
 }
 defer newFile.Close()
 if _, err := newFile.Write(fileBytes); err != nil {
  renderError(w, "CANT_WRITE_FILE", http.StatusInternalServerError)
  return
 }
 w.Write([]byte("SUCCESS"))

這樣可以了. 你可以對這個簡單的例子進行測試,使用虛擬的文件上傳 HTML 頁面,cURL 或者工具例如 postman [1]

這裏是完整的代碼示例 這裏 [2]

結論

這是又一個證明了 Go 如何允許用户為 Web 編寫簡單而強大的軟件,而不必像處理其他語言和生態系統中固有的無數抽象層。

在接下來的篇幅中,我將展示一些在我第一次使用 Go 語言編寫正式的 Web 應用中其他細節,敬請期待。;)

// 根據 reddit 用户 lstokeworth 的反饋對部分代碼進行了修改。謝謝:)

資源

完整代碼示例 [3]

via: https://zupzup.org/go-http-file-upload-download/ 作者: zupzup [4] 譯者: fengchunsgit [5] 校對: polaris1119 [6]

本文由 GCTT [7] 原創編譯, Go 中文網 [8] 榮譽推出

參考資料

[1]

postman: https://www.getpostman.com/

[2]

這裏: https://github.com/zupzup/golang-http-file-upload-download

[3]

完整代碼示例: https://github.com/zupzup/golang-http-file-upload-download

[4]

zupzup: https://zupzup.org/about/

[5]

fengchunsgit: https://github.com/fengchunsgit

[6]

polaris1119: https://github.com/polaris1119

[7]

GCTT: https://github.com/studygolang/GCTT

[8]

Go 中文網: https://studygolang.com/

往期推薦

歡迎關注「 幽鬼 」,像她一樣做團隊的核心。