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

シェアする

こんにちは。Go入門ブログの第26回です。
今回も引き続き、GoにおけるWEB開発の基礎となる知識を解説していきたいと思います。

以下の記事では、net/httpパッケージを使用したWEBサーバの構築方法と、net/urlパッケージを使用したURLのパース・生成方法を解説しました。

【Go入門】net/httpパッケージを使ったWEBサーバの構築とHTTPリクエストハンドラの実装
【Go入門】net/urlパッケージによるURLのパースと生成

今回からは2回に渡り、HTTPクライアントの実装、つまりHTTPリクエストの送信処理方法を解説していきます。
今回の記事では、主にGETリクエストの送信方法を解説します。
解説に当たりnet/urlパッケージの機能を使用する箇所がありますので、必要に応じて上記リンク先の記事を参照してください。

GoにおけるHTTPクライアント処理の実装方法

GoにおけるHTTPクライアントの実装には、サーバの実装と同様にnet/httpを使用できます。
HTTPリクエストの送信方法は、GET/POSTともに、大きく以下の2通りの方法に分けられます。

  • net/httpパッケージに定義されている関数を使用する方法
  • net/httpパッケージに定義されているClient構造体に実装されたメソッドを使用する方法

具体的な送信方法をみていく前に、http.Client構造体について簡単に解説します。

http.Client構造体

http.Client構造体は、その名の通りHTTPクライアントを表します。
net/httpパッケージに以下のように定義されています。

type Client struct {
     Transport RoundTripper
     CheckRedirect func(req *Request, via []*Request) error
     Jar CookieJar
     Timeout time.Duration
}

Transportプロパティは、名前の通りHTTPトランスポートであり、ローレベルのHTTP通信の実態を表します。
file://プロトコルを表すhttp.fileTransportや、oauth2認証を扱うoauth2.Transportなどが指定できます。
nilの場合は、http.DefaultTransport(デフォルトトランスポート)が使用されます。
型はhttp.RoundTripperという独自インターフェイスですが、これについての詳細な解説は割愛します。

CheckRedirectプロパティは、リダイレクトを処理するためのポリシーを指定します。
引数reqとviaを持つ関数型です。
CheckRedirectがnil以外の場合、クライアントはHTTPリダイレクトをたどる前にこの関数を実行します。

Jarプロパティは、クッキージャーを指定します。
Jarは送信リクエストにクッキーを挿入するために使用され、レスポンスのクッキー値で更新されます。

Timeoutプロパティは、このクライアントによるリクエストの制限時間を指定します。
タイムアウトがゼロの場合は、タイムアウトしないことを意味します。

GETリクエストの送信

それでは、具体的なGETリクエストの送信方法をみていきましょう。

http.Get関数を使用する

もっとも簡単なGETリクエストの送信方法は、http.Get関数を使用することです。

func Get(url string) (resp *Response, err error)

http.Get関数は、引数で指定されたURLにGETを発行します。
リダイレクトが多すぎる場合、またはHTTPプロトコルエラーが発生した場合はエラーが返されます。

以下は実装例です。
まずは、リクエストを受け取るサーバーを以下のように記述します。

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"
)

func main() {

    // URLを生成
    u := &url.URL{}
    u.Scheme = "http"
    u.Host = "localhost:8080"
    // url文字列
    uStr := u.String()

    // GETリクエスト発行
    rsp, err := http.Get(uStr)
    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>

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

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

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

func (c *Client) Get(url string) (resp *Response, err error)

http.Get関数と同様に、引数で指定されたURLにGETを発行します。
というより、前述のhttp.Get関数の実態が、DefaultClient.Getのラッパーです。

したがって使用感はhttp.Get関数とほぼ同じですが、タイムアウト値やクッキー値などを指定したい場合にはこちらを使用することになります。

それでは実装例です。
先ほどのserver.goはそのままに、client.goを以下のように書き換えてみてください。

package main

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

func main() {

    // URLを生成
    u := &url.URL{}
    u.Scheme = "http"
    u.Host = "localhost:8080"
    // url文字列
    uStr := u.String()

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

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

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

21行目でClient構造体を生成し、24行目で生成したClient型を使用してGetメソッドを発行しています。
このコードを実行すると、先ほどとまったく同じ出力が得られます。

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

Client構造体にはもう一つ、Getメソッドを送信するためのDoメソッドが実装されています。

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

Doメソッドは、引数にurl文字列ではなくRequest型の構造体を取ります。
Request構造体は、サーバーによって受信された、あるいはクライアントによって送信されるHTTPリクエストを表します。

Request構造体は、リクエストヘッダやformデータ、net.url.URL構造体型のURL情報など、様々な情報をフィールドとして保持しています。
したがって、Getメソッドでは制御できない細かい設定が必要な場合にDoメソッドを使用することになります。

それでは実装例をみてみましょう。
先ほどと同様、server.goは変更せずclient.goを以下のように書き換えてみてください。

package main

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

func main() {

    // URLを生成
    u := &url.URL{}
    u.Scheme = "http"
    u.Host = "localhost:8080"
    // url文字列
    uStr := u.String()

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

    // 生成したURLを元にRequest構造体を生成
    req, _ := http.NewRequest("GET", uStr, nil)

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

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

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

    // GETリクエスト発行
    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))
}

25行目でhttp.NewRequestを生成し、32行目でHeaderプロパティのUser-Agent情報を書き換えています。
また、28-29行目と35-36行目で更新前後のリクエストヘッダ情報をそれぞれ出力し、内容を比較できるようにしています。
なお、リクエストヘッダの内容の出力には、httputil.DumpRequestOut関数を使用しています。

それではclient.goを実行してみてください。

$ go run client.go
【デフォルトのヘッダ情報】
GET / HTTP/1.1
Host: localhost:8080
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip

【変更後のヘッダ情報】
GET / HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MAFSJS; rv:11.0) like Gecko
Accept-Encoding: gzip

    <!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の情報が変更されていることが確認できるかと思います。

終わりに

GETリクエストの送信には3通りの方法があることを解説しました。
デフォルトクライアントで問題なければhttp.Get関数を、タイムアウト値やクッキー値を設定したければClient構造体のGetメソッドを、より細かくリクエストを制御したい場合はClient構造体のDoメソッドを使用する、と整理していただければ問題ありません。
次回は引き続きPOSTリクエストの送信方法を解説したいと思います。

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

シェアする

フォローする

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