隨著互聯網的發展,文件上傳功能越來越成為了Web應用程序中必不可少的功能之一。而在Golang中,實現高效文件上傳並不困難。本文將介紹使用Golang實現高效文件上傳的技巧與方法,涵蓋多個方面,讓你在實際工作中能夠更加方便地處理文件上傳相關的任務。
一、使用multipart/form-data實現文件上傳
處理文件上傳最常用的方式就是使用multipart/form-data格式提交表單。multipart/form-data格式允許我們在一個HTTP請求中同時上傳多個二進位文件,同時還能夠傳輸文本數據。
在Golang中,我們可以使用net/http包的multipart來處理文件上傳。具體的流程可以分為以下幾個步驟:
第一步,定義一個multipart.Writer來構建multipart/form-data格式的請求體。
buf := new(bytes.Buffer)
writer := multipart.NewWriter(buf)
第二步,構建表單欄位和文件欄位,並將它們添加到multipart.Writer中。
file, err := os.Open("/path/to/file")
if err != nil {
panic(err)
}
defer file.Close()
part, err := writer.CreateFormFile("file", file.Name())
if err != nil {
panic(err)
}
_, err = io.Copy(part, file)
if err != nil {
panic(err)
}
writer.WriteField("name", "John Doe")
writer.WriteField("age", "30")
上面的代碼中,我們首先打開需要上傳的文件並創建一個multipart.Writer實例。接著,將這個文件加入到multipart.Writer中。最後,為表單添加一個name欄位和一個age欄位。
第三步,生成multipart/form-data格式的請求並發送到伺服器。
req, err := http.NewRequest("POST", "http://example.com/upload", buf)
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(body))
最後,我們將生成的multipart/form-data格式請求體添加到一個HTTP請求中並發送到伺服器。在發送請求時,需要設置Content-Type頭為multipart/form-data。最後,我們從伺服器響應中讀取數據。
二、使用io.Pipe實現高效文件上傳
除了上面提到的方法,我們還可以使用io.Pipe實現高效文件上傳。使用io.Pipe可以讓我們直接從一個io.Reader中讀取數據,並將其寫入到另一個io.Writer中,這樣就可以邊讀邊寫,避免將整個文件讀入內存。
下面是使用io.Pipe實現文件上傳的示例代碼:
file, err := os.Open("/path/to/file")
if err != nil {
panic(err)
}
defer file.Close()
pr, pw := io.Pipe()
writer := multipart.NewWriter(pw)
go func() {
defer pw.Close()
part, err := writer.CreateFormFile("file", filepath.Base(file.Name()))
if err != nil {
panic(err)
}
io.Copy(part, file)
writer.Close()
}()
req, err := http.NewRequest("POST", "http://example.com/upload", pr)
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(body))
上面的代碼中,首先打開需要上傳的文件。接著,創建一個io.Pipe實例。我們把pipe的一個端點傳遞給multipart writer,而另一端點傳遞給HTTP 客戶端。接著我們啟動一個goroutine,這個goroutine 會把文件內容寫入到pipe writer中。
當我們的管道是完整的時候(文件被寫入完成),multipart writer 會生成請求的完整主體,而客戶端會收到一個請求,其中包含可訪問這個文件上傳的方式例如POST,PUT等。
三、使用io.MultiReader實現大文件高效上傳
如果需要上傳的文件比較大(例如100MB以上),我們可以使用io.MultiReader和io.Pipe組合在一起實現更高效的上傳。
流程如下所示:先將文件分成多個塊,每個塊大小為4MB,依次讀取每個塊並使用io.Pipe寫入到io.MultiReader中,最後再將io.MultiReader傳遞給http.NewRequest的Body參數,發送請求。
const fileChunk = 4 << 20 // 4MB
file, err := os.Open("/path/to/file")
if err != nil {
panic(err)
}
defer file.Close()
fi, err := file.Stat()
if err != nil {
panic(err)
}
numChunks := int(math.Ceil(float64(fi.Size()) / float64(fileChunk)))
pr, pw := io.Pipe()
mw := io.MultiWriter(pw)
writer := multipart.NewWriter(mw)
errCh := make(chan error)
for i := 0; i < numChunks; i++ {
chunkSize := int(math.Min(fileChunk, float64(fi.Size()-int64(i*fileChunk))))
buf := make([]byte, chunkSize)
_, err := file.ReadAt(buf, int64(i*fileChunk))
if err != nil && err != io.EOF {
panic(err)
}
part, err := writer.CreateFormFile("chunk", filepath.Base(file.Name()))
if err != nil {
panic(err)
}
go func() {
defer writer.Close()
_, err := io.Copy(part, bytes.NewReader(buf))
errCh <- err
}()
addFormFields(writer, i)
pr, pw = io.Pipe()
writerFromPreviousChunk, err := writer.CreatePart(textproto.MIMEHeader{})
if err != nil {
panic(err)
}
go func(prevPw *io.PipeWriter) {
defer prevPw.Close()
_, err := io.Copy(writerFromPreviousChunk, pr)
errCh <- err
}(pw)
writer = multipart.NewWriter(mw)
}
pr.Close()
go func() {
defer pw.Close()
_, err := io.Copy(pw, file)
errCh <- err
}()
writer.Close()
req, err := http.NewRequest("POST", "http://example.com/upload", mw)
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if err := <-errCh; err != nil {
panic(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(body))
上面的代碼中,我們首先計算文件需要分成多少個塊,然後先使用io.Pipe將第一個塊寫入到io.MultiWriter中。接著,我們構建multipart/form-data格式的請求體,並為每個塊設置一個name欄位和一個index欄位,表示塊的序號。
對於每個塊,我們都啟動一個goroutine將其寫入到multipart.Writer中。每個塊的寫入都是並行的,這樣可以極大地提高文件上傳的效率。同時,為了避免將整個文件讀入內存,我們使用io.Pipe實現邊讀邊寫。
最後,我們將生成的multipart/form-data格式請求體添加到一個HTTP請求中並發送到伺服器。在發送請求時,需要設置Content-Type頭為multipart/form-data。最後,我們從伺服器響應中讀取數據。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/257083.html