編輯:主要問題原來是實際的上傳程序,而不是發生的死鎖,這只是由錯位引起的wg.Wait()
我正在嘗試通過他們的 API 將檔案上傳到在線檔案托管服務 (https://anonfiles.com/)。上傳檔案大小限制為 20GB。
我可以使用下面的代碼上傳一個大約 2KB 的簡單文本檔案。但是,如果我嘗試對更大的檔案執行相同操作,比方說,大約 2MB,我會從他們的 API 中收到以下錯誤:No file chosen。
我認為這是因為代碼(下面)沒有等待 go 例程正確完成,所以我添加了一個等待組。然后我從 Go: 得到了這個錯誤fatal error: all goroutines are asleep - deadlock!。
我嘗試洗掉下面似乎導致死鎖的 WaitGroup;但是 go 例程下面的代碼將在 go 例程實際完成之前運行。
洗掉 WaitGroup 后,我仍然可以上傳 KB 大小的檔案,但更大的檔案無法正確上傳到檔案托管,因為我No file chosen從他們的 API 收到錯誤。
package main
import (
"fmt"
"io"
"log"
"math/rand"
"mime/multipart"
"net/http"
"os"
"sync"
"time"
)
func main() {
client := http.Client{}
// Upload a >2MB wallpaper.
file, err := os.Open("./wallpaper.jpg")
if err != nil {
log.Fatal(err)
}
defer file.Close()
reader, writer := io.Pipe()
multipart := multipart.NewWriter(writer)
/*
Added Waitgroup to make sure the routine properly finishes. Instead, causes deadlock.
wg := new(sync.WaitGroup)
wg.Add(1)
*/
go func() {
fmt.Println("Starting Upload...")
defer wg.Done()
defer writer.Close()
defer multipart.Close()
part, err := multipart.CreateFormFile("file", file.Name())
if err != nil {
log.Fatal(err)
}
fmt.Println("Copying...")
if _, err = io.Copy(part, file); err != nil {
log.Fatal(err)
}
}()
fmt.Println("The code below will run before the goroutine is finished; without the WaitGroup.")
req, err := http.NewRequest(http.MethodPost, "https://api.anonfiles.com/upload", reader)
if err != nil {
log.Fatal(err)
}
req.Header.Add("Content-Type", multipart.FormDataContentType())
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
wg.Wait()
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
}
我研究了幾個問題,但似乎沒有一個適用于我的問題。是什么導致這個鎖定?有什么不同的做法?也許這是一些菜鳥錯誤,任何建議或幫助將不勝感激。
uj5u.com熱心網友回復:
長話短說
設定Content-Length請求的標頭。
一個作業演示附在這個答案的末尾。
除錯
我認為死鎖問題在這里并不重要。您的目的是將檔案上傳到https://anonfiles.com/。所以我會專注于除錯上傳問題。
首先,讓我們上傳一個檔案curl:
curl -F "[email protected]" https://api.anonfiles.com/upload
有用。
然后讓我們上傳與您的演示相同的檔案,它因誤導性回應而失敗:
{
"status": false,
"error": {
"message": "No file chosen.",
"type": "ERROR_FILE_NOT_PROVIDED",
"code": 10
}
}
現在讓我們用 https://httpbin.org/post 替換目標https://api.anonfiles.com/upload以便我們可以比較請求:
{
"args": {},
"data": "",
"files": {
"file": "aaaaaaaaaa\n"
},
"form": {},
"headers": {
- "Accept": "*/*",
- "Content-Length": "197",
- "Content-Type": "multipart/form-data; boundary=------------------------bd4a81e725230fa6",
"Accept-Encoding": "gzip",
"Content-Type": "multipart/form-data; boundary=2d4e7969789ed6ef6ff3e7b815db3aa040fd3994a34fbaedec85240dc5af",
"Host": "httpbin.org",
- "User-Agent": "curl/7.81.0",
- "X-Amzn-Trace-Id": "Root=1-63747739-2c1dab1b122b7e3a4db8ca79"
"Transfer-Encoding": "chunked",
"User-Agent": "Go-http-client/2.0",
"X-Amzn-Trace-Id": "Root=1-63747872-2fbc85f81c6dde7e5b2091c4"
},
"json": null,
"origin": "47.242.15.156",
"url": "https://httpbin.org/post"
}
突出的區別是send而 go 應用程式curl發送。"Content-Length": "197""Transfer-Encoding": "chunked"
讓我們嘗試修改 go 應用程式以發送Content-Length標頭:
package main
import (
"bytes"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"strings"
)
func main() {
source := strings.NewReader(strings.Repeat("a", 1<<21))
buf := new(bytes.Buffer)
multipart := multipart.NewWriter(buf)
part, err := multipart.CreateFormFile("file", "test.txt")
if err != nil {
log.Fatal(err)
}
if _, err := io.Copy(part, source); err != nil {
log.Fatal(err)
}
multipart.Close()
req, err := http.NewRequest(http.MethodPost, "https://api.anonfiles.com/upload", buf)
if err != nil {
log.Fatal(err)
}
req.Header.Add("Content-Type", multipart.FormDataContentType())
// The following line is not required because the http client will set it
// because the request body is a bytes.Buffer.
// req.ContentLength = int64(buf.Len())
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
}
有用!
缺點是它必須先將請求體復制到記憶體中。在我看來,這是不可避免的,因為它需要知道請求體的大小。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/535735.html
標籤:去僵局协程
上一篇:解組yaml時保持精度
