【Go入門】net/httpパッケージを使ったHTTPクライアントの実装(2) ~ POSTリクエストの送信

シェアする

こんにちは。Go入門ブログの第27回です。

前回から2回に渡り、net/httpパッケージを使用したHTTPクライアントの実装方法を解説しています。
今回の記事では、主にPOSTリクエストの送信方法を解説します。
以下の記事内容を踏襲しますので、必要に応じてご参照ください。

【Go入門】net/httpパッケージを使ったWEBサーバの構築とHTTPリクエストハンドラの実装
【Go入門】ioパッケージ ~ 入出力処理の抽象化と共通化
【Go入門】net/httpパッケージを使ったHTTPクライアントの実装(1) ~ GETリクエストの送信

POSTリクエストの送信

POSTもGETと同じく、http.Client構造体に実装されたメソッドによってリクエストを送信できます。
また、デフォルトクライアントで問題ない場合は、これらのメソッドをラップしたhttpパッケージ直下の関数を使用できます。

net/httpパッケージによるPOSTリクエストには、大きく5つの方法があります。

http.Post関数を使用する

POSTリクエストを送信する一つ目の方法は、http.Post関数を使用することです。

func Post(url, contentType string, body io.Reader) (resp *Response, err error)

http.Post関数は、引数で指定されたURLにPOSTを発行します。
この際、POSTするデータをio.Reader型で引数に渡す必要があります。
また、呼び出し元で読み取りが終わったら、レスポンスボディを閉じる必要があります。

以下は実装例です。
まず、前回のサンプルと同様に、リクエストを受け取るサーバーを以下のように記述してください。

package main

import (
    "io"

    "net/http"
)

func hogeHandler(w http.ResponseWriter, req *http.Request) {
    // HTMLテキストをhttp.ResponseWriterへ書き込む
    io.WriteString(w, `
    <!DOCTYPE html>
    <html lang="ja">
    <head>
      <meta charset="UTF-8">
      <title>Go | net/httpパッケージ</title>
    </head>
    <body>
      <h1>Hello, World!</h1>
      <p>これはnet/httpで実装したlocalhostのWEBサーバです</p>
    </body>
    </html>
`)
}

func main() {
    // localhostへのリクエストをhogeHandler関数で処理する
    http.HandleFunc("/", hogeHandler)
    // localhost:8080でサーバー処理開始
    http.ListenAndServe(":8080", nil)
}

次に、クライアント側のコードを以下のように記述します。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
    "strings"
)

func main() {

    // URLを生成
    u := &url.URL{}
    u.Scheme = "http"
    u.Host = "localhost:8080"
    // url文字列
    uStr := u.String()
    // ポストデータ
    values := url.Values{}
    values.Add("q1", "hoge")
    values.Add("q2", "fuga")

    // POSTリクエスト発行
    rsp, err := http.Post(uStr, "application/x-www-form-urlencoded", strings.NewReader(values.Encode()))
    if err != nil {
        fmt.Println(err)
        return
    }
    // 関数を抜ける際に必ずresponseをcloseするようにdeferでcloseを呼ぶ
    defer rsp.Body.Close()

    // レスポンスを取得し出力
    body, _ := ioutil.ReadAll(rsp.Body)
    fmt.Println(string(body))
}

上記のserver.goを実行し、サーバを起動した状態で、client.goを実行してください。

$ go run client.go

    <!DOCTYPE html>
    <html lang="ja">
    <head>
      <meta charset="UTF-8">
      <title>Go | net/httpパッケージ</title>
    </head>
    <body>
      <h1>Hello, World!</h1>
      <p>これはnet/httpで実装したlocalhostのWEBサーバです</p>
    </body>
    </html>

GET時と同様、期待通りのHTMLレスポンスが得られていることが確認できます。

http.PostForm関数を使用する

http.PostForm関数は、http.Post関数と比較し、より単純化された機能といえます。

func PostForm(url string, data url.Values) (resp *Response, err error)

POSTするデータをnet/urlパッケージのValues型で組み立て、そのまま引数に渡すことができます。
http.Post関数と違い、io.Reader型に変換する必要はありません。
ただし、Content-Typeヘッダーは application/x-www-form-urlencodedに設定されており、他のヘッダを設定することはできません。

それでは実装例を見てみましょう。
先ほどのclient.goの25行目で、http.Post関数を実行している箇所を以下のように書き換えてみてください。

    // 変更前
    // rsp, err := http.Post(uStr, "application/x-www-form-urlencoded", strings.NewReader(values.Encode()))
    
    // 変更後
    rsp, err := http.PostForm(uStr, values)
}

書き換え後にコードを実行すると、先ほどと同じ出力が得られます。
Post関数と比べて、url構造体のデータを変換することなくそのまま使用していることがお解りいただけるかと思います。

Client構造体のPostメソッドを使用する

続いて、Client構造体に実装されたPostメソッドを解説します。
Client.Postの定義は以下の通りです。

func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error)

前述のhttp.Post関数は、DefaultClient.Postのラッパーです。
したがって、事前にクライアント構造体を生成すること以外はhttp.Post関数と同様に使用できます。

それではクライアントサイドの実装例です。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
    "strings"
    "time"
)

func main() {

    // URLを生成
    u := &url.URL{}
    u.Scheme = "http"
    u.Host = "localhost:8080"
    // url文字列
    uStr := u.String()
    // ポストデータ
    values := url.Values{}
    values.Add("q1", "hoge")
    values.Add("q2", "fuga")

    // タイムアウトを30秒に指定してClient構造体を生成
    cli := &http.Client{Timeout: time.Duration(30) * time.Second}

    // POSTリクエスト発行
    rsp, err := cli.Post(uStr, "application/x-www-form-urlencoded", strings.NewReader(values.Encode()))
    if err != nil {
        fmt.Println(err)
        return
    }
    // 関数を抜ける際に必ずresponseをcloseするようにdeferでcloseを呼ぶ
    defer rsp.Body.Close()

    // レスポンスを取得し出力
    body, _ := ioutil.ReadAll(rsp.Body)
    fmt.Println(string(body))
}

26行目でClient構造体を生成し、29行目で生成したClient型を使用してPostリクエストを発行しています。
このコードも実行すれば、サーバから正常なレスポンスが得られるはずです。

Client構造体のPostFormメソッドを使用する

Postと同じく、PostFormメソッドClient構造体に実装されています。

func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error)

もうお解りと思いますが、http.PostForm関数はDefaultClient.PostFormのラッパーです。
タイムアウト値やクッキー値などを指定したい場合は、こちらを使用するとよいでしょう。

それでは実装例です。
先ほどのclient.goの26行目で、cli.Post関数を実行している箇所を以下のように書き換えてみてください。

    // 変更前
    //rsp, err := cli.Post(uStr, "application/x-www-form-urlencoded", strings.NewReader(values.Encode()))

    // 変更後
    rsp, err := cli.PostForm(uStr, values)
}

書き換え後にコードを実行すると、先ほどと同じ出力が得られます。

Client構造体のDoメソッドを使用する

Postリクエスト送信の最後の方法は、Client構造体のDoメソッドを使用することです。

func (c *Client) Do(req *Request) (*Response, error)

Doメソッドは、前回のGETでも使用しました。
POSTの場合も、特徴や基本的な使い方は同様と考えて問題ありません。
PostやPostFormでは扱えないリクエストの細やかな制御が必要な場合に、Doメソッドを使用することになります。

Doメソッドは、引数にurl文字列ではなく、Request型の構造体を取ります。
POSTリクエストを送信する場合は、Request構造体を生成する際に、第1引数としてメソッドタイプに“POST”を指定します。
また、http.Post関数と同様に、リクエストボディをio.Reader型で渡す必要があります。

それでは、クライアントサイドのコードを以下のように書き換えてみてください。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "net/http/httputil"
    "net/url"
    "strings"
    "time"
)

func main() {

    // URLを生成
    u := &url.URL{}
    u.Scheme = "http"
    u.Host = "localhost:8080"
    // url文字列
    uStr := u.String()
    // ポストデータ
    values := url.Values{}
    values.Add("q1", "hoge")
    values.Add("q2", "fuga")

    // タイムアウトを30秒に指定してClient構造体を生成
    cli := &http.Client{Timeout: time.Duration(30) * time.Second}

    // 生成したURLを元にRequest構造体を生成
    req, _ := http.NewRequest("POST", uStr, strings.NewReader(values.Encode()))

    // リクエストにヘッダ情報を追加
    req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MAFSJS; rv:11.0) like Gecko")
    req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

    // リクエストヘッダの内容を出力
    header, _ := httputil.DumpRequestOut(req, true)
    fmt.Println(string(header))

    // POSTリクエスト発行
    rsp, err := cli.Do(req)
    if err != nil {
        fmt.Println(err)
        return
    }
    // 関数を抜ける際に必ずresponseをcloseするようにdeferでcloseを呼ぶ
    defer rsp.Body.Close()

    // レスポンスを取得し出力
    body, _ := ioutil.ReadAll(rsp.Body)
    fmt.Println(string(body))
}

30行目で、io.Reader型のポストデータを渡しhttp.NewRequestを生成しています。
また、33-34行目ではHeaderプロパティのUser-AgentとContent-Typeを書き換えていますが、これはPostメソッドで可能なContent-Typeの指定をDoメソッドでも行うことができ、他のプロパティも細かく指定可能であることを示しています。

このコードを実行すると、以下の出力が得られます。

$ go run client.go
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MAFSJS; rv:11.0) like Gecko
Content-Length: 15
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip

q1=hoge&q2=fuga

    <!DOCTYPE html>
    <html lang="ja">
    <head>
      <meta charset="UTF-8">
      <title>Go | net/httpパッケージ</title>
    </head>
    <body>
      <h1>Hello, World!</h1>
      <p>これはnet/httpで実装したlocalhostのWEBサーバです</p>
    </body>
    </html>

User-AgentおよびContent-Typeに、指定した値がセットされていることが確認できます。
また、url.Valuesで組み立てたデータがリクエストヘッダに付与されていることも確認できます。

終わりに

POSTの送信はGETよりも多くの方法がありますが、使い分けにはさほど差はありません。
デフォルトクライアントで問題なければhttp.Post関数またはhttp.PostForm関数を使用します。
タイムアウト値やクッキー値などを設定したければ、Client構造体を生成した上でPostメソッドまたはPostFormメソッドを使用すればよいでしょう。
更に細かく制御が必要な場合は、Client構造体のDoメソッドを使用してください。

スポンサーリンク
スポンサーリンク
スポンサーリンク

シェアする

フォローする

スポンサーリンク
スポンサーリンク