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)
によってルーティングを書くのだけど、少しめんどくさそう...詳しく知りたい人は以下を参照
メイン文
メイン文はこんな感じ。
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でレスポンスが返される。