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

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

近我使用 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

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

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

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/


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

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/thq0krrBaR6IpyvZuAH7rw