概要
このエントリは、「Enterprise "hello, world" 2018 Advent Calendar 2018」の12/16向けのものです。このAdvent Calendarでは、複数個のエントリにまたがる話の流れも鑑みつつ、なるべく1エントリで1つのトピックをカバーできるようにする予定です。
このエントリで記載するトピックは、Go-lang+GinでAPIサーバを作る一歩目です。1つのAPIを作って、テストします。
前提
おことわり
- このEnterpfise "hello, world"シリーズは、ネタのためのエントリです。実環境でそのまま利用ことを目的とはしていません。
- 動かしやすさを優先してセキュリティを意図的に低くする設定など入れてありますのでご注意ください。
想定読者
「Enterprise "hello, world" 2018」的なネタとしては、下記のような状況を想定しています。
前日のフロントがアクセスするAPIサーバを作らなくてはいけない。
準備(Go-lang)
go
OSなどは、EHW2018のこれまでのエントリと同じです。go-langはまだ使っていなかったので、インストールします。
$ sudo apt-get install golang
$ go version
go version go1.10.4 linux/amd64
Ubuntu 18.04の標準パッケージなので、最新の1.11より少し古いですが、今回の目的ではよしとします。
パッケージ管理ツールdep
$ go get -u github.com/golang/dep/cmd/dep
このプロジェクトを作るディレクトリに移動して、
~/go/src$ mkdir ehw2018.io
$ cd ehw2018.io/
$ dep init
Importing configuration from govendor. These are only initial constraints, and are further refined during the solve process.
Locking in (a5b47d3) for transitive dep gopkg.in/yaml.v2
Locking in (57fdcb9) for transitive dep github.com/mattn/go-isatty
Locking in master (b4a75ba) for transitive dep golang.org/x/sys
Locking in 1.0.0 (36b1496) for transitive dep github.com/json-iterator/go
Locking in master (22d885f) for transitive dep github.com/gin-contrib/sse
Locking in (c88ee25) for transitive dep github.com/ugorji/go
Locking in (5a0f697) for transitive dep github.com/golang/protobuf
Using ^1.3.0 as constraint for direct dep github.com/gin-gonic/gin
Locking in v1.3.0 (b869fe1) for direct dep github.com/gin-gonic/gin
Locking in v8.18.1 (5f57d22) for transitive dep gopkg.in/go-playground/validator.v8
Gin
Ginは、Go-lang向けのWebフレームワークです。
Go-langで使えるWebフレームワークには、比較記事をぱっと見た感じこのほかにも、いっぱい機能がある系のRevelやbeego、シンプル系のecho、goji、Martiniなんかがありそうですが、このエントリでは、以下の点によりGinを使用します。
- 標準のnet/httpだけでもいいけれども、もう少し機能が欲しいこと
- API層だけでよいので、シンプルなものでよいこと
筆者がBombay Sapphireというジンをロックでいただくのが好きであること
source
Ginのサイトにあるチュートリアルを眺めながら書いてみると、こんな感じになりました。
package main
import (
"fmt"
)
import (
"github.com/fvbock/endless"
"github.com/gin-gonic/gin"
)
var (
Version string
Revision string
)
func setupRouter() *gin.Engine {
router := gin.Default()
// Global middleware
router.Use(gin.Logger())
// Routing
router.StaticFile("/", "./index.html")
router.GET("/api/greeting", func(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"message": "hello, world",
})
})
return router
}
func main() {
fmt.Println("Greetings Server : Version:" + Version + " Revision:" + Revision)
endless.ListenAndServe(":8080", setupRouter())
}
上記コードで、GET /api/greeting で、挨拶文を返すことのほか、下記に対応しています。
- ミドルウェアで、Loggerを指定しています。
- ルーティングのセットアップは一つの関数にして、テストしやすくしています。
- "/"へのアクセスで、静的ファイルのindex.htmlを返すようにしています。
- ビルドされたバージョンとリビジョン(Gitのcommit ID)を出力するため、変数を用意し、後述のMakefile内の方法で値を渡して埋め込んでいます。
Makefile
Makefileは、下記のとおりです。ビルド時の変数の埋め込み方などは、「Go でツール書くときの Makefile 晒す」を参考にさせていただきました。
NAME := greetings-server
VERSION := v0.0.1
REVISION := $(shell git rev-parse --short HEAD)
SRCS := $(shell find . -type f -name '*.go')
LDFLAGS := -ldflags="-s -w -X \"main.Version=$(VERSION)\" -X \"main.Revision=$(REVISION)\" -extldflags \"-static\""
all: bin/$(NAME)
depinit:
dep init
depensure:
dep ensure
bin/$(NAME): $(SRCS)
go build -a -tags greetings-server -installsuffix greetings-server $(LDFLAGS) -o bin/$(NAME)
run:
go run greetings-server.go
release-run: bin/$(NAME)
GIN_MODE=release ./bin/greetings-server
check:
curl http://localhost:8080/api/greeting
echo ""
fmt:
go fmt greetings-server.go
test:
go test
デバッグ用の実行
make runします。
$ make run
go run greetings-server.go
Greetings Server : Version: Revision:
[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET / --> ehw2018.io/vendor/github.com/gin-gonic/gin.(*RouterGroup).StaticFile.func1 (4 handlers)
[GIN-debug] HEAD / --> ehw2018.io/vendor/github.com/gin-gonic/gin.(*RouterGroup).StaticFile.func1 (4 handlers)
[GIN-debug] GET /api/greeting --> main.setupRouter.func1 (4 handlers)
14940 :8080
アクセス
curlでアクセスしてみます。
$ make check
curl http://localhost:8080/api/greeting
{"message":"hello, world"}echo ""
動いてますね。
テスト
こんな感じでテストを書きます。
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPingRoute(t *testing.T) {
router := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/greeting", nil)
router.ServeHTTP(w, req)
json := `{"message":"hello, world"}`
assert.Equal(t, 200, w.Code)
assert.Equal(t, json, w.Body.String())
}
動かしてみましょう。
$ make test
go test
[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET / --> ehw2018.io/vendor/github.com/gin-gonic/gin.(*RouterGroup).StaticFile.func1 (4 handlers)
[GIN-debug] HEAD / --> ehw2018.io/vendor/github.com/gin-gonic/gin.(*RouterGroup).StaticFile.func1 (4 handlers)
[GIN-debug] GET /api/greeting --> ehw2018%2eio.setupRouter.func1 (4 handlers)
[GIN] 2018/12/22 - 16:03:24 | 200 | 100.536μs | | GET /api/greeting
[GIN] 2018/12/22 - 16:03:24 | 200 | 226.777μs | | GET /api/greeting
PASS
ok ehw2018.io 0.022s
テストが成功しました。
まとめ
このエントリでは、「Enterprise "hello, world" 2018 Advent Calendar 2018」(EHW2018)の16日目として、Go-lang+GinでAPIサーバを作る一歩目をトピックとして取り上げました。
本エントリのソースコードは、https://github.com/hrkt/greetings-server/releases/tag/0.0.1にあげてあります。
EHW2018のネタとしては、このあと、15日目, 16日目の部品をk8sにデプロイする予定です。