あいつの日誌β

働きながら旅しています。

gin 入門(5)

前回 CLI で記事を追加する機能を追加しました。 今回は SERVER から記事一覧と記事詳細を取得する処理を追加します。

取り急ぎ JSON を返す処理を追加する

edit server/router.go:

func AddRoute(r *gin.Engine, dbmap *gorp.DbMap) *gin.Engine {
    r.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })
    r.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "status":  "posted",
            "message": "message",
        })
    })
    return r
}

edit server/router_test.go: 引数に dbmap を追加したので修正する

package main

import (
    "github.com/coopernurse/gorp"
    "github.com/gin-gonic/gin"
    "github.com/okamuuu/gin-tutorial/shared"
    "github.com/stretchr/testify/assert"
    "io/ioutil"
    "net/http"
    "testing"
    "time"
)

func testRequest(t *testing.T, url string) {
    resp, err := http.Get(url)
    defer resp.Body.Close()
    assert.NoError(t, err)

    body, ioerr := ioutil.ReadAll(resp.Body)
    assert.NoError(t, ioerr)
    assert.Equal(t, "pong", string(body), "resp body should match")
    assert.Equal(t, "200 OK", resp.Status, "should get a 200")
}

func TestRun(t *testing.T) {
    var dsn string = "root:@tcp(127.0.0.1:3306)/test_db"
    dbmap := shared.NewDbMap(dsn)
    shared.CreateTablesIfNotExists(dbmap)
    defer dropAndClose(dbmap)

    router := gin.New()
    AddRoute(router, dbmap)
    go func() {
        assert.NoError(t, router.Run())
    }()
    // have to wait for the goroutine to start and run the server
    // otherwise the main thread will complete
    time.Sleep(5 * time.Millisecond)

    assert.Error(t, router.Run(":8080"), "already running")
    testRequest(t, "http://localhost:8080/ping")
}

func dropAndClose(dbmap *gorp.DbMap) {
    dbmap.DropTablesIfExists()
    dbmap.Db.Close()
}

動作確認

% go test router*
ok      command-line-arguments  0.048s

実装

package main

import (
    "database/sql"
    "github.com/coopernurse/gorp"
    "github.com/gin-gonic/gin"
    "github.com/okamuuu/gin-tutorial/shared"
    "log"
)

func AddRoute(r *gin.Engine, dbmap *gorp.DbMap) *gin.Engine {
    r.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })
    r.GET("/articles", func(c *gin.Context) {
        var articles []shared.Article
        _, err := dbmap.Select(&articles, "select title, created from articles order by id desc")
        if err != nil {
            log.Println("DB Error: ", err)
            c.String(500, "Internal Server Error")
            return
        }
        content := make([]interface{}, len(articles))
        for i, article := range articles {
            content[i] = map[string]interface{}{"title": article.Title, "created": article.Created}
        }
        c.JSON(200, content)
    })
    r.GET("/articles/:id", func(c *gin.Context) {
        id := c.Param("id")
        var article shared.Article
        err := dbmap.SelectOne(&article, "select * from articles where id=?", id)
        if err == sql.ErrNoRows {
            c.String(404, "Not Found")
            return
        } else if err != nil {
            log.Println("DB Error: ", err)
            c.String(500, "Internal Server Error")
            return
        }
        c.JSON(200, article)
    })

    return r
}

動作確認

% go run main.go router.go

curl で確認します。DBに適当なデータをインサートしておいてください。

% curl -s localhost:8080/articles | xargs echo
[{created:1440587591,title:this is markdown},{created:1440587072,title:this is markdown},{created:1440562341,title:this is markdown},{created:1440561336,title:this is markdown},{created:1440561201,title:this is markdown},{created:1440468516,title:Go is awesome},{created:1440468506,title:go},{created:1440468369,title:title2},{created:1440468360,title:title}]
% curl -s localhost:8080/articles/7 | xargs echo
{id:7,title:this is markdown,desc:outline\n\n## header 2\n\nparagraph\n,created:1440562341,published:0}

以上です。

おわりに

最初は gin の tutorial を書こうと思ったのですが、題材が微妙だったせいか、全く gin の tutorial になってない結末になってしまいました。

とりあえずエラーハンドリングが適当だったのと interface{} や string <=> []byte あたりを少し補足したほうがいいかも。

とりあえず一旦寝かせて Go の知見が増えたらまとめて github に公開しようとおもいます。