GoでHTTPクライアントを書く時のURLの組み立て方

この記事は、Go2 Advent Calendar 2017 の10日目の記事です。

外部API用のHTTPクライアント/HTTPリクエストを書いていて、URLの組み立て方で気付いた細かいこと。

サンプル

本題の前に instagram には、
https://www.instagram.com/natgeo/?__a=1
のようにユーザーのプロフィール情報が jsonで返ってくるエンドポイントがあります。

# example
$ curl "https://www.instagram.com/natgeo/?__a=1" | jq .user.biography
"Experience the world through the eyes of National Geographic photographers."

(ドキュメントに載っていない為変更になる可能性はありますが、instagram app の登録も必要ないので知っておくと便利!)

URLの組み立て方

ここから本題。
HTTP クライアントを書いていて、

url := fmt.Sprintf("%s/%s", endpoint, username)
url := endpoint + "/" + username

このようなコードで、下のようなミスに気付かず時間を費やしたことが何度かあります。。

  • 「エラーが返ってくると思ったら、 https://api.instagram.com/v1/username が、 /抜けて https://api.instagram.com/v1username になってた.. :cry:
  • 「あれ、コンフィグのURLホスト名に / ついてる..  https://www.instagram.com/ or https://www.instagram.com :thinking:

標準パッケージで楽に

標準パッケージを使って書くように統一しています。

var endpoint = "https://www.instagram.com"

// endpoint のスキームでミスがあったら、ここでチェックできる。
u, err := url.Parse(endpoint)
if err != nil {
    return err
}

// 複数のエンドポイントもこの書き方で統一して、 `/` の処理を任せる。
u.Path = path.Join(u.Path, username)

// 元のURLにクエリパラメータがついていた場合の上書きを防げる。
q := u.Query()
q.Set("__a", "1")
u.RawQuery = q.Encode()

req, err := http.Get(u.String())
...

稀に path/filepath が使われているものを見ますが、改めて path, path/filepath パッケージの使い方の違いを把握しておくと良いと思います。

path パッケージのディスクリプションにあることが全てですが、一応。

Package path implements utility routines for manipulating slash-separated paths.

The path package should only be used for paths separated by forward slashes, such as the paths in URLs. This package does not deal with Windows paths with drive letters or backslashes; to manipulate operating system paths, use the path/filepath package.

ref.
- golang.org/pkg/path/
- Golang で物理ファイルの操作に path/filepath でなく path を使うと爆発します。

他にも外部API用のHTTPクライアントを幾つか書いています。

GoでのAPIクライアントの実装パターン等については、
公開されているパッケージが official / unofficial 含め一通りあると思うので、いくつか見てみると良いと思います!