Edited at

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

More than 3 years have passed since last update.


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

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

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

$ 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 の構文を任意のコード(今のところは golang, Python, PHP, JavaScript, Java, Objective-C, Vim Script)に変換してくれるもので,どういうことかというと「Web API は curl で表現すればいいんじゃね?」ということらしい。


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

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


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


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

C:>go run gitio0.go

2015/08/08 12:00:00 Invalid url:

あれ? おかしいなぁ。ん~,じゃあちょっと出力をいじって...


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


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 がちゃんと渡ってないんだ。ひとしきり悩んだ後,以下のようにすればいいことに気がついた。


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 されてなかったんだね(この問題は修正されたようです)。これで

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 にかえればいいらしい。結果は以下の通り


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


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

では,楽しい夏休みを!