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

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

詳細はこちら
Go

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

HTTP

HTTP(Hypertext Transfer Protocol)とはweb上でHTML等のコンテンツを交換するために使われるアプリケーション層の通信プロトコルです。

Q&A

解決済

1回答

1908閲覧

GoでPOSTリクエストのデータを取得する方法

d_tutuz

総合スコア730

Go

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

HTTP

HTTP(Hypertext Transfer Protocol)とはweb上でHTML等のコンテンツを交換するために使われるアプリケーション層の通信プロトコルです。

0グッド

0クリップ

投稿2019/11/22 10:01

Go で POST リクエストされたデータを Read する方法がわからないです。

*http.RequestParseForm() メソッドを用いることが一般的ですが、以下のような net/http パッケージを使用しない場合で、適切な扱い方がわからないです。

教えていただきたいこと

net/http パッケージを使用せず、POST リクエストのデータを取得する方法を教えていただきたいです。

実装

https://github.com/GoesToEleven/GolangTraining/blob/master/27_code-in-process/42_HTTP/02_http-server/i05_not-writing_error-in-code/main.go

以下のコードが例です。上記の URL に記載されている URL と同様です。

go

1package main 2 3import ( 4 "bufio" 5 "fmt" 6 "io" 7 "log" 8 "net" 9 "strconv" 10 "strings" 11) 12 13func handleConn(conn net.Conn) { 14 defer conn.Close() 15 scanner := bufio.NewScanner(conn) 16 i := 0 17 headers := map[string]string{} 18 var url, method string 19 for scanner.Scan() { 20 ln := scanner.Text() 21 fmt.Println(ln) 22 23 if i == 0 { 24 fs := strings.Fields(ln) 25 method := fs[0] 26 url = fs[1] 27 fmt.Println("METHOD", method) 28 fmt.Println("URL", url) 29 } else { 30 // in headers now 31 // when line is empty, header is done 32 if ln == "" { 33 break 34 } 35 fs := strings.SplitN(ln, ": ", 2) 36 headers[fs[0]] = fs[1] 37 } 38 39 i++ 40 } 41 42 // parse body 43 44 // ここがポイントと思われる 45 if method == "POST" || method == "PUT" { 46 amt, _ := strconv.Atoi(headers["Content-Length"]) 47 buf := make([]byte, amt) 48 // 単に ReadFull ではデータを読み込むことができない 49 io.ReadFull(conn, buf) 50 // in buf we will have the POST content 51 fmt.Println("BODY:", string(buf)) 52 } 53 // ここまで 54 55 // response 56 body := ` 57<!DOCTYPE html> 58<html lang="en"> 59<head> 60 <meta charset="UTF-8"> 61 <title></title> 62</head> 63<body> 64 <form method="POST"> 65 <input type="text" name="key" value=""> 66 <input type="submit"> 67 </form> 68</body> 69</html> 70 ` 71 72 io.WriteString(conn, "HTTP/1.1 200 OK\r\n") 73 fmt.Fprintf(conn, "Content-Length: %d\r\n", len(body)) 74 io.WriteString(conn, "\r\n") 75 io.WriteString(conn, body) 76} 77 78func main() { 79 server, err := net.Listen("tcp", ":9000") 80 if err != nil { 81 log.Fatalln(err.Error()) 82 } 83 defer server.Close() 84 85 for { 86 conn, err := server.Accept() 87 if err != nil { 88 log.Fatalln(err.Error()) 89 } 90 go handleConn(conn) 91 } 92}
  • クライアントの POST リクエスト

このときクライアントの curl で以下のように POST リクエストを送信したとします。

curl -v http://localhost:9000 -X POST -d "Sample message."
  • サーバに出力されるメッセージ

しかしながらサーバ側には以下のように POST リクエストで送信されているデータを読み込むことができません。

POST / HTTP/1.1 METHOD POST URL / Host: localhost:9000 User-Agent: curl/7.55.1 Accept: */* Content-Length: 4 Content-Type: application/x-www-form-urlencoded

"BODY:": Sample message. が表示されることを想定しているのですが、POST リクエストのデータが読み込めない理由がわからず、教えていただけると助かります。

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

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

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

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

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

guest

回答1

0

ベストアンサー

なぜ標準ライブラリを使わずに書いているのかは学習目的と仮定します。

まずは、「io.ReadFull」の戻り値のerror値の内容を確認するように修正してください。もしnilではないのであればその内容を追記してください。
「io.ReadFull」の次の行が実行されない可能性もあります。

io.ReadFullの次に処理が進むのかを確認し、
戻り値のerrorをログに出すなりしてその内容を教えて下さい。

「io.ReadFull」の次の行が実行されない可能性の原因として考えられるのは
HTTP1.1以降はConnectionヘッダを省略したリクエストはコネクションをキープしようとするという挙動です。
HTTP1.1の接続仕様(MDN)

curlの「-H `Connection: close'」などを付け足して見てみるのも良いかもしれません。

追記

動くようなコードに修正してみました。
net/textprotoのReaderの場合、必要以上に読むことはないのでこういう用途に適しています。

go

1package main 2 3import ( 4 "bufio" 5 "fmt" 6 "io" 7 "log" 8 "net" 9 "net/textproto" 10 "strconv" 11 "strings" 12) 13 14func handleConn(conn net.Conn) { 15 defer conn.Close() 16 reader := bufio.NewReader(conn) 17 scanner := textproto.NewReader(reader) 18 i := 0 19 headers := map[string]string{} 20 var url, method string 21 for { 22 ln, err := scanner.ReadLine() 23 if err != nil { 24 log.Println(err) 25 return 26 } 27 28 if i == 0 { 29 fs := strings.Fields(ln) 30 method = fs[0] 31 url = fs[1] 32 fmt.Println("METHOD", method) 33 fmt.Println("URL", url) 34 } else { 35 // in headers now 36 // when line is empty, header is done 37 if ln == "" { 38 break 39 } 40 fs := strings.SplitN(ln, ": ", 2) 41 headers[fs[0]] = fs[1] 42 } 43 44 i++ 45 } 46 47 // parse body 48 49 // ここがポイントと思われる 50 if method == "POST" || method == "PUT" { 51 amt, _ := strconv.Atoi(headers["Content-Length"]) 52 buf := make([]byte, amt) 53 // 単に ReadFull ではデータを読み込むことができない 54 _, err := io.ReadFull(reader, buf) 55 if err != nil { 56 fmt.Println("error:", err) 57 } 58 // in buf we will have the POST content 59 fmt.Println("BODY:", string(buf)) 60 } 61 // ここまで 62 // response 63 body := ` 64<!DOCTYPE html> 65<html lang="en"> 66<head> 67 <meta charset="UTF-8"> 68 <title></title> 69</head> 70<body> 71 <form method="POST"> 72 <input type="text" name="key" value=""> 73 <input type="submit"> 74 </form> 75</body> 76</html> 77 ` 78 79 io.WriteString(conn, "HTTP/1.1 200 OK\r\n") 80 fmt.Fprintf(conn, "Content-Length: %d\r\n", len(body)) 81 io.WriteString(conn, "\r\n") 82 io.WriteString(conn, body) 83} 84 85func main() { 86 server, err := net.Listen("tcp", ":9000") 87 if err != nil { 88 log.Fatalln(err.Error()) 89 } 90 defer server.Close() 91 92 for { 93 conn, err := server.Accept() 94 if err != nil { 95 log.Fatalln(err.Error()) 96 } 97 go handleConn(conn) 98 } 99}

投稿2019/11/22 12:05

編集2019/11/22 13:52
nobonobo

総合スコア3367

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

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

d_tutuz

2019/11/22 12:45 編集

> なぜ標準ライブラリを使わずに書いているのかは学習目的と仮定します。 仮定のとおりです。ありがとうございます。 ご指摘があった点を修正してみましたが、挙動に変更はありませんでした。 ```go // ここがポイントと思われる if method == "POST" || method == "PUT" { amt, _ := strconv.Atoi(headers["Content-Length"]) buf := make([]byte, amt) // 単に ReadFull ではデータを読み込むことができない _, err := io.ReadFull(conn, buf) if err != nil { log.Fatalln(err.Error()) } // in buf we will have the POST content fmt.Println("BODY:", string(buf)) } // ここまで ``` HTTP 1.1 の Keep-Alive が原因なのか、と思い HTTP 1.0 で通信してみたのですが、こちらも同様で...。確認したときのコマンドは以下です。 #### HTTP/1.0 での通信時 - クライアント ``` curl -v http://localhost:9000 -X POST -d "Sample message." --http1.0 ``` - サーバ側の出力 (プロトコルとして HTTP/1.0 であることはサーバも認識できている) ``` POST / HTTP/1.0 METHOD POST URL / Host: localhost:9000 User-Agent: curl/7.55.1 Accept: */* Content-Length: 15 Content-Type: application/x-www-form-urlencoded ``` #### -H "Connection: close" を明記したときの通信時 - クライアント ``` curl -v http://localhost:9000 -X POST -d "Sample message." -H "Connection: close" ``` - サーバ側の出力 ``` POST / HTTP/1.1 METHOD POST URL / Host: localhost:9000 User-Agent: curl/7.55.1 Accept: */* Connection: close Content-Length: 15 Content-Type: application/x-www-form-urlencoded ``` ※Markdownが反映されず、、、すみません
nobonobo

2019/11/22 13:15

あー。わかりました。Scannerがバッファリングですでにすべてのリクエストコンテンツを読んでしまっているからです。 もうコンテンツの最後まで読見込み済みである可能性が高く、ReadFullは条件を満たせずにずっと待ち続けます。
nobonobo

2019/11/22 13:29

それともう一つ。method := fs[0]にて「var method」とは別の小さなスコープにmethodを再定義してしまっています。なので外側のmethodには値は空文字のままです。
nobonobo

2019/11/22 13:30

おそらく、一度Scanを始めてしまったScannerは別の方法でバッファ内容にアクセスする方法はありません。ヘッダーの読み込みにはもう少し低レベルな方法で読み出す必要があるでしょう。
d_tutuz

2019/11/22 13:52 編集

nobonobo さん > method := fs[0]にて「var method」とは別の小さなスコープにmethodを再定義してしまっています。なので外側のmethodには値は空文字のままです。 そうですね、確認不足でした。`method = fs[0]` などとしてクロージャとして参照しないといけないですね。。。 > おそらく、一度Scanを始めてしまったScannerは別の方法でバッファ内容にアクセスする方法はありません。ヘッダーの読み込みにはもう少し低レベルな方法で読み出す必要があるでしょう。 こちらありがとうございます。ヘッダーの読み込みに別の方法を検討してみます。
nobonobo

2019/11/22 13:54

標準のnet/httpライブラリはnet/textprotoのReaderを使ってヘッダーをパースしています。net/textprotoを使った例を追記しました。
d_tutuz

2019/11/22 13:55

nobonobo さん net/textproto パッケージ勉強になりました。感謝です、ありがとうございます!!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問