這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
package mainimport ( "fmt" "io" "net/http" "log")// 擷取大小的借口type Sizer interface { Size() int64}// hello world, the web serverfunc HelloServer(w http.ResponseWriter, r *http.Request) { if "POST" == r.Method { file, _, err := r.FormFile("userfile") if err != nil { http.Error(w, err.Error(), 500) return } defer file.Close() f,err:=os.Create("filenametosaveas") defer f.Close() io.Copy(f,file)fmt.Fprintf(w, "上傳檔案的大小為: %d", file.(Sizer).Size()) return } // 上傳頁面 w.Header().Add("Content-Type", "text/html") w.WriteHeader(200) html := `<form enctype="multipart/form-data" action="/hello" method="POST"> Send this file: <input name="userfile" type="file" /> <input type="submit" value="Send File" /></form>` io.WriteString(w, html)}func main() { http.HandleFunc("/hello", HelloServer) err := http.ListenAndServe(":12345", nil) if err != nil { log.Fatal("ListenAndServe: ", err) }}用戶端上傳檔案代碼: func Upload() (err error) { // Create buffer buf := new(bytes.Buffer) // caveat IMO dont use this for large files, \ // create a tmpfile and assemble your multipart from there (not tested) w := multipart.NewWriter(buf) // Create file field fw, err := w.CreateFormFile("file", "helloworld.go") //這裡的file很重要,必須和伺服器端的FormFile一致 if err != nil { fmt.Println("c") return err } fd, err := os.Open("helloworld.go") if err != nil { fmt.Println("d") return err } defer fd.Close() // Write file field from file to upload _, err = io.Copy(fw, fd) if err != nil { fmt.Println("e") return err } // Important if you do not close the multipart writer you will not have a // terminating boundry w.Close() req, err := http.NewRequest("POST","http://192.168.2.127/configure.go?portId=2", buf) if err != nil { fmt.Println("f") return err } req.Header.Set("Content-Type", w.FormDataContentType()) var client http.Client res, err := client.Do(req) if err != nil { fmt.Println("g") return err } io.Copy(os.Stderr, res.Body) // Replace this with Status.Code check fmt.Println("h") return err}
處理檔案上傳
你想處理一個由使用者上傳的檔案,比如你正在建設一個類似Instagram的網站,你需要儲存使用者拍攝的照片。這種需求該如何?呢?
要使表單能夠上傳檔案,首先第一步就是要添加form的enctype
屬性,enctype
屬性有如下三種情況:
application/x-www-form-urlencoded 表示在發送前編碼所有字元(預設)
multipart/form-data 不對字元編碼。在使用包含檔案上傳控制項的表單時,必須使用該值。
text/plain 空格轉換為 “+” 加號,但不對特殊字元編碼。
所以,表單的html代碼應該類似於:
<html><head><title>上傳檔案</title></head><body><form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post"> <input type="file" name="uploadfile" /> <input type="hidden" name="token" value="{...{.}...}"/> <input type="submit" value="upload" /></form></body></html>
在伺服器端,我們增加一個handlerFunc:
http.HandleFunc("/upload", upload)// 處理/upload 邏輯func upload(w http.ResponseWriter, r *http.Request) { fmt.Println("method:", r.Method) //擷取請求的方法 if r.Method == "GET" { crutime := time.Now().Unix() h := md5.New() io.WriteString(h, strconv.FormatInt(crutime, 10)) token := fmt.Sprintf("%x", h.Sum(nil)) t, _ := template.ParseFiles("upload.gtpl") t.Execute(w, token) } else { r.ParseMultipartForm(32 << 20) file, handler, err := r.FormFile("uploadfile") if err != nil { fmt.Println(err) return } defer file.Close() fmt.Fprintf(w, "%v", handler.Header) f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666) if err != nil { fmt.Println(err) return } defer f.Close() io.Copy(f, file) }}
通過上面的代碼可以看到,處理檔案上傳我們需要調用r.ParseMultipartForm
,
裡面的參數表示maxMemory
,調用ParseMultipartForm
之後,
上傳的檔案儲存體在maxMemory
大小的記憶體裡面,如果檔案大小超過了maxMemory
,那麼剩下的部分將儲存在系統的臨時檔案中。
我們可以通過r.FormFile
擷取上面的檔案控制代碼,然後執行個體中使用了io.Copy
來隱藏檔。
擷取其他非檔案欄位資訊的時候就不需要調用r.ParseForm
,因為在需要的時候Go自動會去調用。而且ParseMultipartForm
調用一次之後,後面再次調用不會再有效果。
通過上面的執行個體我們可以看到我們上傳檔案主要三步處理:
1. 表單中增加enctype="multipart/form-data"2. 服務端調用`r.ParseMultipartForm`,把上傳的檔案儲存體在記憶體和臨時檔案中3. 使用`r.FormFile`擷取檔案控制代碼,然後對檔案進行儲存等處理。
檔案handler是multipart.FileHeader,裡面儲存了如下結構資訊
type FileHeader struct {Filename stringHeader textproto.MIMEHeader// contains filtered or unexported fields}
我們通過上面的執行個體代碼列印出來上傳檔案的資訊如下
![](images/4.5.upload2.png?raw=true)
列印檔案上傳後伺服器端接受的資訊
用戶端上傳檔案
我們上面的例子示範了如何通過表單上傳檔案,然後在伺服器端處理檔案,
其實Go支援類比用戶端表單功能支援檔案上傳,詳細用法請看如下樣本:
package mainimport ("bytes""fmt""io""io/ioutil""mime/multipart""net/http""os")func postFile(filename string, targetUrl string) error { bodyBuf := &bytes.Buffer{} bodyWriter := multipart.NewWriter(bodyBuf) //關鍵的一步操作 fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename) if err != nil { fmt.Println("error writing to buffer") return err } //開啟檔案控制代碼操作 fh, err := os.Open(filename) if err != nil { fmt.Println("error opening file") return err } //iocopy _, err = io.Copy(fileWriter, fh) if err != nil { return err } contentType := bodyWriter.FormDataContentType() bodyWriter.Close() resp, err := http.Post(targetUrl, contentType, bodyBuf) if err != nil { return err } defer resp.Body.Close() resp_body, err := ioutil.ReadAll(resp.Body) if err != nil { return err } fmt.Println(resp.Status) fmt.Println(string(resp_body)) return nil}// sample usagefunc main() { target_url := "http://localhost:9090/upload" filename := "./astaxie.pdf" postFile(filename, target_url)}
上面的例子詳細展示了用戶端如何向伺服器上傳一個檔案的例子,
用戶端通過multipart.Write把檔案的文字資料流寫入一個緩衝中,然後調用http的Post方法把緩衝傳到伺服器。
如果你還有其他普通欄位例如username之類的需要同時寫入,那麼可以調用multipart的WriteField方法寫很多其他類似的欄位。