網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
前言:
近我使用 Go 語(yǔ)言完成了一個(gè)正式的 Web 應(yīng)用,有一些方面的問(wèn)題在使用 Go 開(kāi)發(fā) Web 應(yīng)用過(guò)程中比較重要。過(guò)去,我將 Web 開(kāi)發(fā)作為一項(xiàng)職業(yè)并且把使用不同的語(yǔ)言和范式開(kāi)發(fā) Web 應(yīng)用作為一項(xiàng)愛(ài)好,因此對(duì)于 Web 開(kāi)發(fā)領(lǐng)域有一些心得體會(huì)。
總的來(lái)說(shuō),我喜歡使用 Go 語(yǔ)言進(jìn)行 Web 開(kāi)發(fā),盡管開(kāi)始一段時(shí)間需要去適應(yīng)它。Go 語(yǔ)言有一些坑,但是正如本篇文章中所要討論的文件上傳與下載,Go 語(yǔ)言的標(biāo)準(zhǔn)庫(kù)與內(nèi)置函數(shù),使得開(kāi)發(fā)是種愉快的體驗(yàn)。
在接下來(lái)的幾篇文章中,我將重點(diǎn)討論我在 Go 中編寫(xiě)生產(chǎn)級(jí) Web 應(yīng)用程序時(shí)遇到的一些問(wèn)題,特別是關(guān)于身份驗(yàn)證/授權(quán)的問(wèn)題。
這篇文章將展示 HTTP 文件上傳和下載的基本示例。我們將一個(gè)有 type 文本框和一個(gè) uploadFile 上傳框的 HTML 表單作為客戶(hù)端。
讓我們來(lái)看下 Go 語(yǔ)言中是如何解決這種在 Web 開(kāi)發(fā)中隨處可見(jiàn)的問(wèn)題的。
代碼示例 首先,我們?cè)诜?wù)器端設(shè)定兩個(gè)路由, /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)) }
我們還將要上傳的目標(biāo)目錄,以及我們接受的最大文件大小定義為常量。注意這里,整個(gè)文件服務(wù)的概念是如此的簡(jiǎn)單 —— 我們僅使用標(biāo)準(zhǔn)庫(kù)中的工具,使用 http.FileServe 創(chuàng)建一個(gè) HTTP 處理程序,它將使用 http.Dir(uploadPath) 提供的目錄來(lái)上傳文件。
現(xiàn)在我們只需要實(shí)現(xiàn) uploadFileHandler 。
這個(gè)處理程序?qū)韵鹿δ埽?/strong>
- 驗(yàn)證文件最大值
- 從請(qǐng)求驗(yàn)證文件和 POST 參數(shù)
- 檢查所提供的文件類(lèi)型(我們只接受圖像和 PDF)
- 創(chuàng)建一個(gè)隨機(jī)文件名
- 將文件寫(xiě)入硬盤(pán)
- 處理所有錯(cuò)誤,如果一切順利返回成功消息
第一步,我們定義處理程序:
func uploadFileHandler() http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
然后,我們使用 http.MaxBytesReader 驗(yàn)證文件大小,當(dāng)文件大小大于設(shè)定值時(shí)它將返回一個(gè)錯(cuò)誤。錯(cuò)誤將被一個(gè)助手程序 renderError 進(jìn)行處理,它返回錯(cuò)誤信息及對(duì)應(yīng)的 HTTP 狀態(tài)碼。
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize) if err := r.ParseMultipartForm(maxUploadSize); err != nil { renderError(w, "FILE_TOO_BIG", http.StatusBadRequest) return }
如果文件大小驗(yàn)證通過(guò),我們將檢查并解析表單參數(shù)類(lèi)型和上傳的文件,并讀取文件。在本例中,為了清晰起見(jiàn),我們不使用花哨的 io.Reader 和 io.Writer 接口,我們只是簡(jiǎn)單的將文件讀取到一個(gè)字節(jié)數(shù)組中,這點(diǎn)我們后面會(huì)寫(xiě)到。
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 }
現(xiàn)在我們成功的驗(yàn)證了文件的大小,并且讀取了文件,接下來(lái)我們?cè)摍z驗(yàn)文件的類(lèi)型了。一種廉價(jià)但是并不安全的方式,只檢查文件擴(kuò)展名,并相信用戶(hù)沒(méi)有改變它,但是對(duì)于一個(gè)正式的項(xiàng)目來(lái)講不應(yīng)該這么做。
幸運(yùn)的是,Go 標(biāo)準(zhǔn)庫(kù)提供給我們一個(gè) http.DetectContentType 函數(shù),這個(gè)函數(shù)基于 mimesniff 算法,只需要讀取文件的前 512 個(gè)字節(jié)就能夠判定文件類(lèi)型。
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 }
在實(shí)際應(yīng)用程序中,我們可能會(huì)使用文件元數(shù)據(jù)做一些事情,例如將其保存到數(shù)據(jù)庫(kù)或?qū)⑵渫扑偷酵獠糠?wù)——以任何方式,我們將解析和操作元數(shù)據(jù)。這里我們創(chuàng)建一個(gè)隨機(jī)的新名字(這在實(shí)踐中可能是一個(gè) UUID)并將新文件名記錄下來(lái)。
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)
馬上就大功告成了,只剩下一個(gè)關(guān)鍵步驟-寫(xiě)文件。如上文所提到的,我們只需要復(fù)制讀取的二進(jìn)制文件到一個(gè)新創(chuàng)建的名為 newFile 的文件處理程序里。
如果所有部分都沒(méi)問(wèn)題,我們給用戶(hù)返回一個(gè) 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"))
這樣可以了. 你可以對(duì)這個(gè)簡(jiǎn)單的例子進(jìn)行測(cè)試,使用虛擬的文件上傳 HTML 頁(yè)面,cURL 或者工具例如 postman [1] 。
這里是完整的代碼示例 這里 [2]
結(jié)論 這是又一個(gè)證明了 Go 如何允許用戶(hù)為 Web 編寫(xiě)簡(jiǎn)單而強(qiáng)大的軟件,而不必像處理其他語(yǔ)言和生態(tài)系統(tǒng)中固有的無(wú)數(shù)抽象層。
在接下來(lái)的篇幅中,我將展示一些在我第一次使用 Go 語(yǔ)言編寫(xiě)正式的 Web 應(yīng)用中其他細(xì)節(jié),敬請(qǐng)期待。;)
// 根據(jù) reddit 用戶(hù) lstokeworth 的反饋對(duì)部分代碼進(jìn)行了修改。謝謝:)
原文鏈接:https://blog.51cto.com/u_15773567/5652662
相關(guān)推薦
- 2023-02-04 Go語(yǔ)言中websocket的使用demo分享_Golang
- 2022-05-17 Springboot+Maven做啟動(dòng)類(lèi)與業(yè)務(wù)模塊分離的架構(gòu)模式
- 2022-07-18 Linux安裝配置nginx
- 2022-10-21 使用nginx進(jìn)行負(fù)載均衡的搭建全過(guò)程_nginx
- 2022-09-30 react?redux的原理以及基礎(chǔ)使用講解_React
- 2023-01-11 Android?nonTransitiveRClass資源沖突問(wèn)題淺析_Android
- 2022-11-05 Flutter實(shí)現(xiàn)一個(gè)支持漸變背景的Button示例詳解_Android
- 2023-03-04 Qt利用tablewidget模擬手指實(shí)現(xiàn)滑動(dòng)_C 語(yǔ)言
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支