こんなに簡単! Goで作るRESTサーバー


Goを始めて4日たった。仕事関係のウェブサービスで、負荷分散のためにバックエンドを冗長化する必要がでてきた。そういうとなんかすごそうだが、高負荷の外部コマンドを叩くだけのサービスを作ればいい。複雑さはない。今回はそのサービスをRESTサーバーとしてGoで作る。

# この記事のゴール

Goで作ったRESTサーバーをLinuxにデプロイし、[httpie](https://github.com/jkbr/httpie)で叩いてみる


# 準備

まず、Macで作ってLinuxにデプロイしようと思うので、Mac, Linux, Windowsにコンパイルできるように、クロスコンパイル可能な状態でGoをインストールする:

```shell
brew install go --cross-compile-common
```

次に、適当にフォルダを作る

```shell
mkdir ~/Desktop/gorest
cd ~/Desktop/gorest/
```

つぎに、service.goファイルを作る

```shell
touch service.go
```

それを、エディタで開く

最後に、$GOPATHをセットしてなければ、とりあえず `~/Desktop/gorest` にしておく

```shell
export GOPATH=$(pwd)
```

# APIの仕様

名前を入力すると、Hello, <名前> で返してくる

* POST /hello

```json:HTTPリクエストのBody
{
	"Name": "Alice"
}
```

```json:HTTPレスポンスのBody
{
	"Result": "Hello, Alice"
}
```

# RESTサーバーを簡単に作れるライブラリを取ってくる

```shell
go get github.com/ant0ine/go-json-rest
```

# サーバー(service.go)のコードを書く

賞味40行くらい

```go:service.go
package main

import (
	"github.com/ant0ine/go-json-rest"
	"log"
	"net/http"
)

// 入力の定義
type postHelloInput struct {
	Name string
}

// 出力の定義
type postHelloOutput struct {
	Result string
}

func postHello(w *rest.ResponseWriter, req *rest.Request) {
	input := postHelloInput{}
	err := req.DecodeJsonPayload(&input)

	// そもそも入力の形式と違うとここでエラーになる
	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// 適当なバリデーション
	if input.Name == "" {
		rest.Error(w, "Name is required", 400)
		return
	}

	log.Printf("%#v", input)

	// 結果を返す部分
	w.WriteJson(&postHelloOutput{
		"Hello, " + input.Name,
	})
}

func main() {
	handler := rest.ResourceHandler{}
	handler.SetRoutes(
		rest.Route{"POST", "/hello", postHello},
	)
	log.Printf("Server started")
	http.ListenAndServe(":9999", &handler) // ポート9999で立ち上がる
}
```

# 実行してみる

`go run server.go` でサーバーを起動する

```shell
go run service.go
```

ターミナルの別タブを開いて、httpieでリクエストを投げてみる

```shell
http -v POST localhost:9999/hello "Content-Type:application/json; charset=UTF-8" Name="Suin"
```

```
POST /hello HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate, compress
Content-Length: 16
Content-Type: application/json; charset=UTF-8
Host: localhost:9999
User-Agent: HTTPie/0.6.0

{
    "Name": "Suin"
}

HTTP/1.1 200 OK
Content-Length: 29
Content-Type: application/json
Date: Fri, 06 Dec 2013 16:10:15 GMT

{
    "Result": "Hello, Suin"
}

```

うまく動いている様子。

# Linux用にビルドする

Linux 64ビット用のバイナリを作る:

```shell
GOOS=linux GOARCH=amd64 go build service.go
```

ビルドが終わると `service` という名前で7MBくらいの実行形式のバイナリができあがる。

# ポート9999が開けてあるLinuxに持ってく

scpとかで転送する

```shell
scp service vagrant:
```

サーバにログインして、今転送したserviceを起動する

```shell
vagrant@suin:~$ ./service
2013/12/07 01:15:02 Server started
```

手元のMacから httpie で叩いてみる

```shell
http -v POST 11.11.11.11:9999/hello "Content-Type:application/json; charset=UTF-8" Name="Suin"
```
```
POST /hello HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate, compress
Content-Length: 16
Content-Type: application/json; charset=UTF-8
Host: 11.11.11.11:9999
User-Agent: HTTPie/0.6.0

{
    "Name": "Suin"
}

HTTP/1.1 200 OK
Content-Length: 29
Content-Type: application/json
Date: Fri, 06 Dec 2013 16:16:15 GMT

{
    "Result": "Hello, Suin"
}
```

Apacheも必要なくファイル一個だけで動くLinuxのRESTサーバーができました!