🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

Q&A

解決済

1回答

658閲覧

Go で 大量のテキストファイルを POST するときの書き方について

EzrealTrueshot

総合スコア389

Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

0グッド

0クリップ

投稿2020/12/30 10:34

編集2020/12/30 14:25

前提・実現したいこと

大量のテキストファイル(1万ファイル以上)を POST したい。

エラーメッセージ

fatal: error: Post "http://xxx.xxx.xxx.xxx:xxxx/upload": dial tcp xxx.xxx.xxx.xxx:xxxx: i/o timeout<nil>

該当のソースコード

うまくいくパターン

go

1func main(){ 2 ary := ["/root/a/a.txt","/root/b/b.txt","/root/c/c.txt",...] // アップロード対象のファイルパスが大量に入っている配列 3 4 for _,v := range ary { 5 postFile(v, "http://xxx.xxx.xxx.xxx:xxxx/upload") 6 } 7} 8 9func postFile(filename string, targetUrl string) error { 10 bodyBuf := &bytes.Buffer{} 11 bodyWriter := multipart.NewWriter(bodyBuf) 12 13 //キーとなる操作 14 fileWriter, err := bodyWriter.CreateFormFile("file-text", filename) 15 if err != nil { 16 fmt.Println("error writing to buffer") 17 return err 18 } 19 20 //ファイルハンドル操作をオープンする 21 fh, err := os.Open(filename) 22 if err != nil { 23 fmt.Println("error opening file") 24 return err 25 } 26 defer fh.Close() 27 28 //iocopy 29 _, err = io.Copy(fileWriter, fh) 30 if err != nil { 31 return err 32 } 33 34 contentType := bodyWriter.FormDataContentType() 35 bodyWriter.Close() 36 37 resp, err := http.Post(targetUrl, contentType, bodyBuf) 38 if err != nil { 39 return err 40 } 41 defer resp.Body.Close() 42 resp_body, err := ioutil.ReadAll(resp.Body) 43 if err != nil { 44 return err 45 } 46 fmt.Println(resp.Status) 47 fmt.Println(string(resp_body)) 48 return nil 49} 50

エラーが出るパターン

go

1func main (){ 2 ary := ["/root/a/a.txt","/root/b/b.txt","/root/c/c.txt",...] // アップロード対象のファイルパスが大量に入っている配列 3 file_upload(ary) 4} 5func file_upload(file_paths []string) { 6 for _, file_path := range file_paths { 7 8 var buf bytes.Buffer 9 w := multipart.NewWriter(&buf) 10 11 f, err := os.Open(file_path) 12 if err != nil { 13 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 14 } 15 defer f.Close() 16 fw, err := w.CreateFormFile("file-text", file_path) 17 if err != nil { 18 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 19 } 20 _, err = io.Copy(fw, f) 21 if err != nil { 22 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 23 } 24 w.Close() 25 req, err := http.NewRequest("POST", "http://xxx.xxx.xxx.xxx:xxxx/upload", &buf) 26 if err != nil { 27 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 28 } 29 req.Header.Set("Authorization", "Bearer YYYY") 30 req.Header.Set("Content-Type", w.FormDataContentType()) 31 32 resp, err := http.DefaultClient.Do(req) 33 defer resp.Body.Close() 34 time.Sleep(time.Second * 1) 35 if err != nil { 36 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 37 } 38 fmt.Println(resp) 39 } 40} 41

希望

file_upload関数の書き方でPOSTしたい(AuthorizationのBearerを使いたいたいめ)のですが、上記の io エラーが出てしまっています。(5ファイル~20ファイル程はきちんとアップロードされ、そのあと詰まった感じになり上記エラーが発生。早すぎるから問題なのかな?思い、送信処理時にsleepを毎回1秒ほどいれてみたが結果は変わらず。)

ioエラーを出さずに、POST(AuthorizationのBearer利用)できる方法がありましたらご教示いただけませんでしょうか?

うまくいくパターンの書き方ですと、ヘッダーの追加が不可能なため何かいい手がありましたら幸いです。

補足情報(FW/ツールのバージョンなど)

Ubuntu18.04
go version go1.15.6 linux/amd64

#追記

頂いたアドバイスを元に修正したバージョン1(func(){}()を追記)

go

1func main (){ 2 ary := ["/root/a/a.txt","/root/b/b.txt","/root/c/c.txt",...] // アップロード対象のファイルパスが大量に入っている配列 3 file_upload(ary) 4} 5func file_upload(file_paths []string) { 6 for _, file_path := range file_paths { 7 func () { 8 var buf bytes.Buffer 9 w := multipart.NewWriter(&buf) 10 11 f, err := os.Open(file_path) 12 if err != nil { 13 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 14 } 15 defer f.Close() 16 fw, err := w.CreateFormFile("file-text", file_path) 17 if err != nil { 18 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 19 } 20 _, err = io.Copy(fw, f) 21 if err != nil { 22 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 23 } 24 w.Close() 25 req, err := http.NewRequest("POST", "http://xxx.xxx.xxx.xxx:xxxx/upload", &buf) 26 if err != nil { 27 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 28 } 29 req.Header.Set("Authorization", "Bearer YYYY") 30 req.Header.Set("Content-Type", w.FormDataContentType()) 31 32 resp, err := http.DefaultClient.Do(req) 33 defer resp.Body.Close() 34 time.Sleep(time.Second * 1) 35 if err != nil { 36 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 37 } 38 fmt.Println(resp) 39 }() 40 } 41}

頂いたアドバイスを元に修正したバージョン(for文の中で関数を回す)

go

1func main (){ 2 ary := ["/root/a/a.txt","/root/b/b.txt","/root/c/c.txt",...] // アップロード対象のファイルパスが大量に入っている配列 3 for _,file_path := range file_paths{ 4 file_upload(file_path) 5 } 6} 7func file_upload(file_path string) { 8 9 var buf bytes.Buffer 10 w := multipart.NewWriter(&buf) 11 12 f, err := os.Open(file_path) 13 if err != nil { 14 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 15 } 16 defer f.Close() 17 fw, err := w.CreateFormFile("file-text", file_path) 18 if err != nil { 19 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 20 } 21 _, err = io.Copy(fw, f) 22 if err != nil { 23 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 24 } 25 w.Close() 26 req, err := http.NewRequest("POST", "http://xxx.xxx.xxx.xxx:xxxx/upload", &buf) 27 if err != nil { 28 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 29 } 30 req.Header.Set("Authorization", "Bearer YYYY") 31 req.Header.Set("Content-Type", w.FormDataContentType()) 32 33 resp, err := http.DefaultClient.Do(req) 34 defer resp.Body.Close() 35 time.Sleep(time.Second * 1) 36 if err != nil { 37 fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 38 } 39 fmt.Println(resp) 40}

ともに詰まってしまい、同じエラーメッセージがクライアント側で表示されます。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

1

ベストアンサー

これが原因です。
Big Sky :: golang では for ループの中で defer してはいけない。

一番目のパターンでは関数が終わるたび defer resp.Body.close() が呼ばれているためタイムアウトが発生しても内部で自動的に TCP コネクションを張り替えてリクエストし直しています。しかし二番目のパターンでは defer resp.Body.close() が期待されるのは for ループを抜けた最後の 1 回のみです。コネクションを張り替える暇もなくタイムアウトです。ではどうするか。記事にもあるように関数で for で回したい処理を切り取ると良いでしょう。

投稿2020/12/30 12:38

A_kirisaki

総合スコア2853

EzrealTrueshot👍を押しています

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

EzrealTrueshot

2020/12/30 14:06

ありがとうございます! 頂いたURLとおりに ``` func(){ }() ``` で編集しなおしたのですが、やはり、途中で止まってしまいます。。。 ほかに何か心当たりがありそうなところがありましたらアドバイスいただけますと幸いです。
A_kirisaki

2020/12/30 14:16

ソースコードを質問に追記お願いします
EzrealTrueshot

2020/12/30 14:17

また、`うまくいくパターン` のように関数の中でfor文をするのではなく、for文のなかで関数を回しても同じ結果でした。
EzrealTrueshot

2020/12/30 14:17

はい。ソースコードを載せます。
EzrealTrueshot

2020/12/30 17:23

fmt.Println("xxxx")でどこが詰まっているのかを調査したところ fmt.Println("req --- start") resp, err := http.DefaultClient.Do(req) fmt.Println("req --- end") ここで詰まっていました。 req --- start が表示されてから、 req --- end が表示されてませんでした。 httpのリクエストの仕方は合っていると思うのですが。。。
A_kirisaki

2021/01/03 18:33

Do と Post のソースコードまで読みにいったんですが謎なんですよね……もうちょっと調べてみます
A_kirisaki

2021/01/03 19:14

export GODEBUG=http2debug=1 でデバッグログが見れるのでなにかおかしなリクエストしてたら追記してみてください。
EzrealTrueshot

2021/01/04 18:51

承知いたしました。 ありがとうございます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問