Go でAPIを書く

目標は
JSONを受け取る→{何らかの処理}→処理結果をJSON形式で返す
これだけのAPIを作ります。 以下のページを参考にしました。 thenewstack.io

パッケージ

まずはインポートするパッケージ

    import (
            "fmt"  
            "html"  
            "log" 
            "net/http"  
            "encoding/json"  
            "github.com/gorilla/mux"  
    )

"fmt"は標準出力に関するパッケージ。
"net/http"は簡単にhttpサーバを実装してくれるパッケージ。
"html"はhtml文をトークン化&解析するもの。
"encoding/json"はスライスなどをJSONエンコードしてくれるパッケージ。
そして最後に"github.com/gorilla/mux"。これは外部パッケージで、httpリクエストが来た時に、リクエストをGoで定義された関数までルーティングしてくれる。無くてもよいのだけど、とても便利なパッケージである。
外部パッケージを使う場合は現在プログラムを書いているディレクトリで

    go get

を実行する必要があります。 ※"gorilla/mux"を入れない場合はデフォルトのmux(multiplexer)によってルーティングを書くのだけど、少しめんどくさそう...詳しく知りたい人は以下を参照

m0t0k1ch1st0ry.com

メイン文

メイン文はこんな感じ。

    func main(){
            router := mux.NewRouter().StrictSlash(true)
            router.HandleFunc("/",Index)
            router.HandleFunc("/todos/{todoId}",TodoShow)
            log.Fatal(http.ListenAndServe(":8080",router))
    }

外部パッケージの"gorilla/mux"を使ってhttpルータを定義します。
router.HandleFuncによってhttpリクエストをそれぞれの関数にルーティングしてあげます。

    router.HandleFunc("{リクエストURL}",{関数名})

って感じの使い方です。
http.ListenAndServe(":8080",router)で8080番ポートでhttpサーバを起動させます。ここの第2引数routerの部分はhandlerを入れる部分、"gorilla/mux"等のhttpルータを使ってなければnilで埋めれば良いらしい。

関数の作成

    func Index(w http.ResponseWriter, r *http.Request){
            fmt.Fprintf(w,"Hello, %q",html.EscapeString(r.URL.Path))
    }
    func TodoShow(w http.ResponseWriter , r *http.Request){
            vars := mux.Vars(r)
            todoId := vars["todoId"]
            fmt.Fprintln(w,"Todo show:",todoId)
    }

今回はリクエストに応じて2つの関数を作りました。(参考ページに乗っているサンプル通りだけど...)
はじめの関数IndexはURLのパスを返すだけの関数。ここにhtmlのレスポンスを書けば、ウェブページが作れる。
2つ目の関数TodoShowはmain文で受け取ったURLの{todoId}を表示する関数になっている。
URLの{todoId}の部分は任意の文字列を入れることができる。
たとえば http://localhost:8080/todos/hogehoge というようにリクエストを送ると、todoIdの部分にはhogehogeが入ってきて、Todo show:hogehogeという文字列が出力される。

JSONでデータを受け取る

ここからは少し応用編
JSON形式のデータを受け取る部分に入る。
http.requestの中で送られてくるデータはhttp.Request.Bodyの中に入っている。 今回はBodyの中身を一度Stringで読み込んで、それをJSONファイルとしてデコードする。(あまりスマートじゃない気がする)

    import (
            "fmt"
            "html"
            "log"
            "net/http"
            "encoding/json"
            "io"
            "bufio"
     
            "github.com/gorilla/mux"
    )
    
    type POSTJSON struct{
            Board string "json:\"board\""
            Player int "json:\"player\""
    }
       
    func TodoIndex(w http.ResponseWriter,r *http.Request){
            request := ""
            rb := bufio.NewReader(r.Body)
            for {
                    s, err := rb.ReadString('\n')
                    request = request + s
                    if err == io.EOF { break }
            }
            u := new(POSTJSON)
            json.Unmarshal(([]byte)(request),u)
            fmt.Println(u.Board)
    }

まず、importパッケージの中に"io""bufio"を追加している(データ読み込みのため)。
forの中身でbufio.NewReader(r.Body)をstringへ変換している。ここまででstringにデータを変換できたので、無理してJSONとして読み込む必要はないきがするが、、、
JSONデータを受けるためには、受け皿になる構造体が必要なので、type POSTJSON structでそのPOSTJSON(名前は何でも良い)構造体を定義している。
u := new(POSTJSON)によって構造体を初期化。
ここで重要な関数Unmarshalの登場。この関数により、stringをbyteに変換し構造体の中にマップする。
これで、送られてきたJSONデータを構造体へと変換することができたので、データへの簡単なアクセスが可能になった。 ちなみに、htmlのFORMで送られてきたデータを受け取りたいだけなら

    r.ParseForm()

でリクエストの中身を解析して、

    for k, v := range r.Form {
            fmt.Println("key:", k)
            fmt.Println("val:", strings.Join(v, ""))
        }

こんな感じで中身を取り出すことができるらしい。JSONでもこんな感じでできないか探してます。

フォームの入力を処理する | build-web-application-with-golang

JSONデータを返す

あとはJSONデータを返すだけ。これは先ほどとは逆に構造体→JSONという形で変換ができます。

    type Todo struct{
            Name string
            Completed bool
            Num int
    }
    type Todos []Todo

    func TodoIndex(w http.ResponseWriter,r *http.Request){
        type Todos []Todo
    
        todos := Todos{
                    Todo{Name: "Host meetup", Completed: true , Num: 1},
                    Todo{Name: "KEET", Completed: false , Num: 2},
            }
            json.NewEncoder(w).Encode(todos)
    }

上記では構造体配列を使っている。このようにjson.NewEncoder(w).Encode(todos)により構造体配列も簡単にJSONに変換することができる。さらに、http.ResponseWriterに変換したJSONを入れているので、リクエスト先にJSONでレスポンスが返される。

これでJSONを受け取ってJSONを返すAPIが完成?!
お疲れ様でした。