這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。最近我使用 Go 語言完成了一個正式的 web 應用,有一些方面的問題在使用 Go 開發 web 應用過程中比較重要。過去,我將 web 開發作為一項職業並且把使用不同的語言和範式開發 web 應用作為一項愛好,因此對於 web 開發領域有一些心得體會。總的來說,我喜歡使用 Go 語言進行 web 開發,儘管開始一段時間需要去適應它。Go 語言有一些坑,但是正如本篇文章中所要討論的檔案上傳與下載,Go 語言的標準庫與內建函數,使得開發是種愉快的體驗。在接下來的幾篇文章中,我將重點討論我在 Go 中編寫生產級 Web 應用程式時遇到的一些問題,特別是關於身分識別驗證/授權的問題。這篇文章將展示HTTP檔案上傳和下載的基本樣本。我們將一個有 `type` 文字框和一個 `uploadFile` 上傳框的 HTML 表單作為用戶端。讓我們來看下 Go 語言中是如何解決這種在 web 開發中隨處可見的問題的。## 程式碼範例首先,我們在伺服器端設定兩個路由,`/upload` 用於檔案上傳, `/files/*` 用於檔案下載。```goconst 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)- 建立一個隨機檔案名稱- 將檔案寫入硬碟- 處理所有錯誤,如果一切順利返回成功訊息第一步,我們定義處理常式:```gofunc uploadFileHandler() http.HandlerFunc {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {```然後,我們使用 `http.MaxBytesReader` 驗證檔案大小,當檔案大小大於設定值時它將返回一個錯誤。錯誤將被一個助手程式 `renderError` 進行處理,它返回錯誤資訊及對應的 HTTP 狀態代碼。```gor.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)if err := r.ParseMultipartForm(maxUploadSize); err != nil {renderError(w, "FILE_TOO_BIG", http.StatusBadRequest)return}```如果檔案大小驗證通過,我們將檢查並解析表單參數類型和上傳的檔案,並讀取檔案。在本例中,為了清晰起見,我們不使用花哨的 `io.Reader` 和 `io.Writer` 介面,我們只是簡單的將檔案讀取到一個位元組數組中,這點我們後面會寫到。```gofileType := 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 個位元組就能夠判定檔案類型。```gofiletype := 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)並將新檔案名稱記錄下來。```gofileName := 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` 資訊。```gonewFile, 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](https://www.getpostman.com/)。這裡是完整的程式碼範例 [這裡](https://github.com/zupzup/golang-http-file-upload-download)## 結論這是又一個證明了 Go 如何允許使用者為 web 編寫簡單而強大的軟體,而不必像處理其他語言和生態系統中固有的無數抽象層。在接下來的篇幅中,我將展示一些在我第一次使用 Go 語言編寫正式的 web 應用中其他細節,敬請期待。;)// 根據 reddit 使用者 `lstokeworth` 的反饋對部分代碼進行了修改。謝謝:)### 資源[完整程式碼範例](https://github.com/zupzup/golang-http-file-upload-download)
via: https://zupzup.org/go-http-file-upload-download/
作者:zupzup 譯者:fengchunsgit 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
3316 次點擊 ∙ 1 贊