概要

GoでAPIサーバーを書こうと思ったらどうなるのかなーっと思い立ったのでやってみた。 APIの受け答えはJSON形式で行い、加えてダッシュボードみたいなUIも提供できるようなものを想定している。 DBとかデザインとかはそれぞれ別のレイヤーになるので今回はリクエストとレスポンスまで。

準備

今回使うのは go-restfulというパッケージ。RESTfulなHTTPサーバーを作るのに使う様子。 https://github.com/emicklei/go-restful

予め go get しておこう。

go get github.com/emicklei/go-restful

似たもので go-json-rest というものもあったが、JSONでの受け答え専用のようだったので今回はパスした。

コード全文

コードを説明して回るのも大変長くなるのでザッと書き出しておく 👻

package main

import (
	"github.com/emicklei/go-restful"
	"io"
	"net/http"
)

// Helloに渡す値を格納する
type resHello struct {
	Name string
	Memo string `json:",omitempty"`
}

// エラーレスポンス用の構造体
type resError struct {
	Error string
}

// "GET /hello" を処理する関数
func getHello(req *restful.Request, resp *restful.Response) {
	// GETのときは text/plain で文字列を返す
	io.WriteString(resp, "please tell me your name to /hello by POST method :)")
}

// "POST /hello" を処理する関数
func postHello(req *restful.Request, resp *restful.Response) {
	// リクエストを resHello に割り当てる
	param := new(resHello)
	req.ReadEntity(&param)

	// 適当なエラーハンドリング
	if param.Name == "" {
		// エラーの内容をJSONで返す
		resp.WriteHeaderAndJson(http.StatusInternalServerError, &resError{"Name is required"}, restful.MIME_JSON)
		return
	}

	// 結果を出力する (自動で application/json が指定される)
	resp.WriteAsJson(&resHello{Name: param.Name})
}

func main() {
	// restfulを初期化してルーティングの設定を行う
	ws := new(restful.WebService)
	ws.Route(ws.GET("/hello").To(getHello))
	ws.Route(ws.POST("/hello").To(postHello))
	restful.Add(ws)

	// 8080ポートで待ち受ける
	http.ListenAndServe(":8080", nil)
}

実行結果

上記のコードは go run でサーバーを立ち上げられる。 立ち上げたサーバーにはブラウザアクセスか、cURLでアクセスできる。

GET /hello

ブラウザやcURLで http://localhost:8080/hello にアクセスすると下記のような結果が得られる。

$ curl -i http://127.0.0.1:8080/hello
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    52  100    52    0     0   3250      0 --:--:-- --:--:-- --:--:--  3250HTTP/1.1 200 OK
Date: Sat, 02 Jan 2016 13:20:14 GMT
Content-Length: 52
Content-Type: text/plain; charset=utf-8

please tell me your name to /hello by POST method :)

ちなみにあえて指定を入れていない /404 Page Not Found となる

$ curl -i http://127.0.0.1:8080/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    19  100    19    0     0     19      0  0:00:01 --:--:--  0:00:01 19000HTTP/1.1 404 Not Found
Date: Sat, 02 Jan 2016 13:20:42 GMT
Content-Length: 19
Content-Type: text/plain; charset=utf-8

404: Page Not Found

POST /hello

cURLを使ってPOSTを叩くと下記のような応答になる。WriteAsJson で Content-type はよしなにやってくれる。 resHelloMemo に指定した omitempty (空の時は切り捨て) は有効な様子。

$ curl -i -H 'Content-Type: application/json' -d '{"Name":"Gopher"}' http://127.0.0.1:8080/hello
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    40  100    23  100    17   1437   1062 --:--:-- --:--:-- --:--:--  1437HTTP/1.1 200 OK
Content-Type: application/json
Date: Sat, 02 Jan 2016 13:11:48 GMT
Content-Length: 23

{
  "Name": "Gopher"
 }

ちなみにエラーハンドリングしている箇所はこのような応答になる。 JSONであることがわかっているのに WriteHeaderAndJsonrestful.MIME_JSON を指定してあげないといけないのは ちょっと微妙かもと思ったが、もしかしたら使いどころが別にあるのかもしれない。

$ curl -i -H 'Content-Type: application/json' -d '{"Name":""}' http://127.0.0.1:8080/hello
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    45  100    34  100    11   2125    687 --:--:-- --:--:-- --:--:--  2125HTTP/1.1 500 Internal Server Error
Content-Type: application/json
Date: Sat, 02 Jan 2016 13:09:46 GMT
Content-Length: 34

{
  "Error": "Name is required"
 }

感想

  • GoでHTTPサーバーと一体なアプリは意外とサクッと作れた。
  • サンプルコードも多めでGoDoc対応にも対応していたので割と安心。
  • go-restful はXMLの受け答えにも対応している様子。
  • WebアプリってPHP(CakePHPやLaravel)とかRuby(Rails、Sinatra)とかで作るイメージが強いけど、
    Goで実運用したりして問題になったりしないかは未知数。net/httpのベンチマークとかあるのかな?

関連リンク