在開發Web應用時,檔案的上傳下載是一個十分常用的功能。基本上每個專案都要重新開發一遍,很囉嗦不說,本身效能也不是很優秀,功能並不強大。
說起來我們自家的應用用的是阿里雲的OSS服務,感覺非常好用,於是為了以後省事,決定使用Golang來開發一個基於http的檔案伺服器,提供類似的服務。
這個服務目前僅僅完成了簡單的檔案上傳和訪問功能。計劃在未來幾個月逐步完善更豐富的功能,諸如檔案去重,圖片處理,分包分路徑等,基本上希望參考oss等雲端儲存服務的功能。
在此期間也經歷了很多坑,現記錄如下:
- Http檔案上傳
- 靜態檔案訪問
- Ajax跨域上傳
- Golang交叉編譯
下面將依次記錄在本次開發中遇到的問題和解決方法。
程式碼:(其中ftpCommand目錄下的ftpBootstrap.go檔案是服務啟動器,同名檔案為linux-x64編譯的可執行檔案)
Http檔案上傳
http上傳自然是最基本的功能。出於web應用的考量,上傳是基於form表單的multipart/form-data形式。
//獲取表單中的檔案
file, handler, err := r.FormFile("file")
if err != nil {
var response = UploadResponse{State: -1, Msg: err.Error(), URL: ""}
response.Send(w)
return
}
//副檔名
fileext := filepath.Ext(handler.Filename)
//用時間戳做檔名防止重名
filename := strconv.FormatInt(time.Now().Unix(), 10) + fileext
//新建檔案
f, _ := os.OpenFile("./upload/"+filename, os.O_CREATE|os.O_WRONLY, 0660)
//儲存檔案
_, err = io.Copy(f, file)
複製程式碼
看下來,Golang儲存檔案比起java來說簡單得多了。自帶提供的io工具十分方便。
上傳檔案之後需要給一個返回資訊。在此使用Json作為返回格式,定義結構體如下:
//UploadResponse 返回訊息
type UploadResponse struct {
State int `json:"state"`
URL string `json:"data"`
Msg string `json:"msg"`
}
複製程式碼
有一點需要注意,Golang對於一些格式約定十分敏感,要將此結構體轉換為Json,只有首字母大寫的欄位才能被轉換到json中。一開始沒有注意這一點,導致轉換出的Json一直為空。糾結了好長時間。而後面的json:"msg"
等備註可以指定轉換成的欄位名。
這樣一個結構體最終返回的Json結果如下:
{
"state": 0,
"data": "/get/1476696601.png",
"msg": "success"
}
複製程式碼
靜態檔案訪問
檔案成功上傳之後,就是訪問的功能了。這裡用的是Golang自帶的FileServer功能。
http.Handle("/get/", http.FileServer(httpDir))
複製程式碼
一開始按照這樣來寫,結果並不好用。經過一番嘗試和搜尋,結果下面的寫法才是正確的:
http.Handle("/get/", http.StripPrefix("/get/", http.FileServer(httpDir)))
複製程式碼
這裡有個http.StripPrefix方法。此方法的功能是減掉指定的路由。比如在此就是將請求開頭的"/get/"給減掉。這樣FileServer才能正確的通過指定的路徑來訪問檔案。
###Ajax跨域上傳
這個說起來是一個前端的問題。 原本的程式碼中使用Jsonp來實現Ajax跨域請求。但是Jsonp本身是個第三方的約定,而且不支援post請求,無法通過post上傳FormData資料。
通過網上的資料,可以使用最新的XHR2來實現跨域訪問。十分簡單方便,可惜只支援ie10以上,不過不管了呀!這必然是歷史的潮流啊!
通過XHR2來實現跨域請求的根本在於新增的幾個Header:
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
第一個指定允許跨域訪問的域名,第二個指定訪問的方法,第三個指定了接受的Header。
鑑於目前只有這麼一個介面,就先寫在了上傳檔案的介面中。按理來說應該是提取出來統一做處理的。
//w define by w http.ResponseWriter
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers",
"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
w.Header().Set("Content-Type", "application/json")
複製程式碼
最後一個Content-Type也是不可少的,否則瀏覽器不會將返回資料作為json來處理。
將Access-Control-Allow-Origin指定為*是一件挺危險的事情。這樣允許所有域名訪問。目前作為測試服務臨時如此。在具體應用中應當指定為特定域名。
Golang交叉編譯
交叉編譯是Golang挺酷炫的一個特性。Golang本身雖然不支援一處編譯,處處執行的跨平臺方式,但是它的交叉編譯真的太方便了。
網上的資料不是很新鮮,很多資料中的方法還需要重新用原始碼進行編譯等等工作。在最新的Golang中,已經完全不需要這些繁瑣的步驟,要交叉編譯,只要一行命令:
GOOS=linux GOARCH=amd64 go build bootstrap.go
複製程式碼
其中GOOS指定作業系統,常用的有darwin(macOS),windows和linux,GOARCH是平臺,支援amd64,386和arm,但是據說arm的支援還不完善。
另外當目標平臺與當前平臺一致時,GOARCH引數是可以省略的。
通過交叉編譯,就可以在我的Macbook上進行開發,打包成一個可執行檔案,扔到Linux伺服器上啟動,簡單快捷。比起java動輒一大堆的配套環境簡直不知道高到哪裡去了。