Help us understand the problem. What is going on with this article?

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

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

では,楽しい夏休みを!

spiegel-im-spiegel
職業エンジニアは無期休業中。 Qiita はめっきり読み専になってます。
https://baldanders.info/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away