【Golang實現檔案伺服器】(一)初始版本

晦若晨曦發表於2017-12-14

在開發Web應用時,檔案的上傳下載是一個十分常用的功能。基本上每個專案都要重新開發一遍,很囉嗦不說,本身效能也不是很優秀,功能並不強大。

說起來我們自家的應用用的是阿里雲的OSS服務,感覺非常好用,於是為了以後省事,決定使用Golang來開發一個基於http的檔案伺服器,提供類似的服務。

這個服務目前僅僅完成了簡單的檔案上傳和訪問功能。計劃在未來幾個月逐步完善更豐富的功能,諸如檔案去重,圖片處理,分包分路徑等,基本上希望參考oss等雲端儲存服務的功能。

在此期間也經歷了很多坑,現記錄如下:

  • Http檔案上傳
  • 靜態檔案訪問
  • Ajax跨域上傳
  • Golang交叉編譯

下面將依次記錄在本次開發中遇到的問題和解決方法。

程式碼:(其中ftpCommand目錄下的ftpBootstrap.go檔案是服務啟動器,同名檔案為linux-x64編譯的可執行檔案)

Github


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動輒一大堆的配套環境簡直不知道高到哪裡去了。

相關文章