学習ノート 名前: Dozi0116

プログラムのことか、ゲームのことか、サイト作ったらそっちに移転したい

GoでREST APIサーバーを作ったときの備忘録

概要

Golangなんもわからない状態からREST APIを提供できるようになったので、その時の備忘録

TL; DR

github.com/julienschmidt/httprouter を使えば簡単に作成できる
net/http とは違い、ハンドラにわたす引数が1つ多いのでそこだけ注意

encoding/json を組み合わせてJSONレスポンスまで作れたよ

完成物

本来は複数ファイルに分かれているけど、わかりやすさのためにまとめた

package main

import (
    "encoding/json"
    "fmt"
    "log"
        "net/http"

    "github.com/julienschmidt/httprouter"
)

// ------------------ レスポンス作成/レンダー部分 ----------------------

type StatusCode int

// レスポンス形式 resultの中に返したいものが含まれる
// interface{} はどんな型でも受け付けるくん
type Response struct {
    Status StatusCode  `json:"status"`
    Result interface{} `json:"result"`
}

// 結果にステータスコードをつけ、JSON化する
// []byteはjson.Marshalの返り値
func CreateJSON (data interface{}, status StatusCode) []byte {
    response := Response{status, data}

    res, err := json.Marshal(response)

    if err != nil {
        log.Panic(err) 
    }

    return res
}

// 200のときはステータスコードを省略できる
func RenderJSONOK(w *http.ResponseWriter, data interface{}) {
    RenderJSON(w, data, http.StatusOK)
}

// レスポンスの原型を受け取り、JSON形式で返す
func RenderJSON(w *http.ResponseWriter, data interface{}, status StatusCode) {
    res := CreateJSON(data, status)
    (*w).Header().Set("Content-Type", "application/json")
        (*w.)WriteHeader(status)
    (*w).Write(res)
}

// ---------------------------------------------------------------------
// ------------------------ APIメソッド部分 ----------------------------

func GetMessageList(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
    RenderJSONOK(&w, "Call GetMessageList!") // -> { "status": 200, "result": "Call GetMessageList!" }
}

func PostMessage(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
    RenderJSON(&w, "coming soon", http.StatusNotFound) // -> { "status": 404, "result": "comming soon" }
}

func GetMessageById(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
    RenderJSON(&w, "coming soon", http.StatusNotFound) // -> { "status": 404, "result": "comming soon" }
}

// -------------------------------------------------------------------
// -------------------- ルーティング登録部分 --------------------------

// ルーティング登録を切り出して見やすくしたつもり
func Registration(router *httprouter.Router) {
    router.GET("/message", GetMessageList)
    router.POST("/message", PostMessage)
    router.GET("/message/:id", GetMessageById)
}

func main() {
    router := httprouter.New()
    Registration(router)

    err := http.ListenAndServe(":8080", router)
    if err != nil {
        log.Panic(err)
    } else {
        fmt.Println("something wrong")
    }
}

解説

httprouterを使う

net/http だけでも、メソッドごとに分割して作れなくも無いっぽいが

func Test(w http.ResponseWriter, r *http.Request) {

    // メソッドチェック
    switch (r.Method) {
        case: http.MethodGet:
            // GET処理
        case: http.MethodPost
            // POST処理
    }
}

func Registration() {
    http.HandleFunc("/", Test) // GET, POST, PUT
}

このように記述することになる。 個人的には、Registrationの中で一発でメソッドを管理できないなど可読性が悪く感じるのでなし。

そこで、調べていたところ、12kスターが付いている github.com/julienschmidt/httprouter を見つけた。 これを使うと、完成コードのように、

func TestGet(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    // GET処理
}

func TestPost(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    // POST処理
}

func Registration(router *httprouter.Router) {
    router.GET("/", TestGet)
    router.POST("/", TestPost)
}

と、メソッドごとに処理を分けて記述することができる。

しかし、httprouterの基本機能として、Paramsが渡ってくる場所が別にあるため、これを受け取れるようにする必要がある。

ここまでで、自由にREST APIを作れるようになった。 ここからは、APIのレスポンスでおなじみのJSONを返すように少してを加える。

encoding/jsonを使う

encoding/jsonは標準で搭載されている。 構造体に一手間加えると、JSONを返せるようになる便利パッケージ。 使い方は以下の通り

// `json:""` を忘れると、Hoge, Fugaなど1文字目が大文字になる(属性名そのままになる)ので注意
type Response struct {
    Hoge int    `json:"hoge"`
    Fuga string `json:"fuga"`
}

func CreateJSON () []byte {
    response := Response{123, "abc"}

    json, err := json.Marshal(response)

    if err != nil {
        panic(err) 
    }

    return json // {"hoge": 123, "fuga": "abc"}
}

これで手軽にJSON形式を生成できるので、これをhttpレスポンスにのせて上げればもう完成。 それで、あちこち関数化などをして出来上がったのが上のコード。

まとめ

便利パッケージがある! 名前わかりにくすぎる! リファレンスちゃんと読まないと型が特定できないからドキュメントなどはちゃんと見よう!

参考にしたところ