Git.io 短縮 URL を golang コードで取得してみる

## Git.io で短縮 URL が取得できるらしい

- [Git・Githubに隠された便利な機能 | GitHub Cheat Sheet（日本語訳） - Qiita](http://qiita.com/sotayamashita/items/1cf05f2a2be3d6fb3388)

これを読んでたら後ろの方に [Git.io] の話が出ていた。このサイトで短縮 URL を生成できるらしい。

- [Git.io: GitHub URL Shortener](https://github.com/blog/985-git-io-github-url-shortener)

API が [curl] で掲載されていて，例えば私の [https://github.com/spiegel-im-spiegel](https://github.com/spiegel-im-spiegel) なら

```shell
$ curl -i "http://git.io" -F "url=https://github.com/spiegel-im-spiegel"
HTTP/1.1 201 Created
Server: Cowboy
Connection: keep-alive
Date: Sat, 08 Aug 2015 02:42:16 GMT
Status: 201 Created
Content-Type: text/html;charset=utf-8
Location: http://git.io/vOj52
Content-Length: 37
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Runtime: 0.210952
X-Node: 871d903e-a8e0-46ff-a96f-ef424385e5ed
X-Revision: b1d9ce07ccb700fc90398edafd397beb8d3bd772
Via: 1.1 vegur

https://github.com/spiegel-im-spiegel
```

てな感じで，ヘッダの Location 要素に短縮 URL が返ってくる仕組みらしい。

って，これって [curl] で書けるんなら golang で表現できるんじゃね？

## cURL as DSL

- [cURL as DSL — cURL as DSL 1.0 documentation](https://shibukawa.github.io/curl_as_dsl/)
- [Shibu's Diary: cURL as DSLとは何だったのか。あるいは細かすぎて伝わらないcURL as DSL。](http://blog.shibu.jp/article/115602749.html)

[cURL as DSL](https://shibukawa.github.io/curl_as_dsl/) とは [curl] の構文を任意のコード（今のところは golang, Python, PHP, JavaScript, Java, Objective-C, Vim Script）に変換してくれるもので，どういうことかというと「[Web API は curl で表現すればいいんじゃね？](http://qiita.com/Hiraku/items/dfda2f8a5353b0742271)」ということらしい。

## さっそく curl を golang に変換してみる

では早速，上述の curl コマンドを golang に変換してみる（ちなみに `-i` は付けない）。こんな感じ。

```go:gitio0.go
package main

import (
	"bytes"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
)

func main() {
	var buffer bytes.Buffer
	writer := multipart.NewWriter(&buffer)
	writer.WriteField("url", "https://github.com/spiegel-im-spiegel")

	resp, err := http.Post("http://git.io", "multipart/form-data; boundary="+writer.Boundary(), &buffer)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	log.Print(string(body))
}
```

まずは何も考えずに動かしてみる。

```shell
C:>go run gitio0.go
2015/08/08 12:00:00 Invalid url:
```

あれ？ おかしいなぁ。ん～，じゃあちょっと出力をいじって...

```go:gitio1.go
package main

import (
	"bytes"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
)

func main() {
	var buffer bytes.Buffer
	writer := multipart.NewWriter(&buffer)
	writer.WriteField("url", "https://github.com/spiegel-im-spiegel")

	resp, err := http.Post("http://git.io", "multipart/form-data; boundary="+writer.Boundary(), &buffer)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("  Status: ", resp.Header.Get("Status"))
	log.Println("Location: ", resp.Header.Get("Location"))
	log.Println("    Body: ", string(body))
}
```

```shell
C:>go run gitio1.go
2015/08/08 12:00:00   Status: 422 Unprocessable Entity
2015/08/08 12:00:00 Location:
2015/08/08 12:00:00     Body: Invalid url:
```

ん？ あっそうか。 url がちゃんと渡ってないんだ。ひとしきり悩んだ後，以下のようにすればいいことに気がついた。

```go:gitio2.go
package main

import (
	"bytes"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
)

func main() {
	var buffer bytes.Buffer
	writer := multipart.NewWriter(&buffer)
	writer.WriteField("url", "https://github.com/spiegel-im-spiegel")
	writer.Close() //writer はちゃんと閉じましょう

	resp, err := http.Post("http://git.io", "multipart/form-data; boundary="+writer.Boundary(), &buffer)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("  Status: ", resp.Header.Get("Status"))
	log.Println("Location: ", resp.Header.Get("Location"))
	log.Println("    Body: ", string(body))
}
```

多分 `buffer` に flush されてなかったんだね（この問題は[修正された](#comment-8cb38c8ea500d627f725)ようです）。これで

```shell
C:>go run gitio2.go
2015/08/08 12:00:00   Status: 201 Created
2015/08/08 12:00:00 Location: http://git.io/vOj52
2015/08/08 12:00:00     Body: https://github.com/spiegel-im-spiegel
```

となり，めでたく短縮 URL が取得できた。

ちなみに，これには `http.PostForm` 関数を使った別解がある。最初の [curl] の式の `-F` を `-d` にかえればいいらしい。結果は以下の通り

```go:gitio0b.go
package main

import (
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
)

func main() {
	values := url.Values{
		"url": {"https://github.com/spiegel-im-spiegel"},
	}

	resp, err := http.PostForm("http://git.io", values)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	log.Print(string(body))
}
```

今回に限れば，こちらのほうがコードがスッキリして分かりやすい。

では，楽しい夏休みを！

[Git.io]: http://git.io/ "git.io"
[curl]: http://curl.haxx.se/ "curl and libcurl"