NEMAPIをGo言語で叩く(3)
今回はGo言語の機能である「goroutine」を使います。
ソースコード
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
type Heartbeat struct {
Code int `json:"code"`
Type int `json:"type"`
Message string `json:"message"`
}
func get_heartbeat(result_c chan Heartbeat, err_c chan error) {
resp, err := http.Get("http://alice2.nem.ninja:7890/heartbeat")
if err != nil {
err_c <- err
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
err_c <- err
return
}
var heartbeat Heartbeat
if err := json.Unmarshal(body, &heartbeat); err != nil {
err_c <- err
} else {
result_c <- heartbeat
}
}
func main() {
result_c := make(chan Heartbeat, 1)
err_c := make(chan error, 1)
go get_heartbeat(result_c, err_c)
fmt.Println("main")
select {
case result := <-result_c:
fmt.Println(result)
case err := <-err_c:
fmt.Println(err)
}
}
実行結果
main
{1 2 ok}
解説
これはこちらの記事で紹介したソースコードを非同期化したものです。
実行結果から、APIを叩くため時間のかかるget_heartbeat()が非同期で実行されているため、メインスレッドで実行しているfmt.Println(“main”)の結果が先に出力されているのがわかります。
応用
Taka Nobu氏の ノード接続に失敗した場合に別ノードを自動選択するプログラムをGo言語で実装してみます。
ソースコード
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"time"
)
type Heartbeat struct {
Code int `json:"code"`
Type int `json:"type"`
Message string `json:"message"`
}
func get_endpoint() string {
/*
実用する場合はこちら
NODES := [...]string{
"alice2.nem.ninja:7890",
"alice3.nem.ninja:7890",
"alice4.nem.ninja:7890",
"alice5.nem.ninja:7890",
"alice6.nem.ninja:7890",
"alice7.nem.ninja:7890"};
*/
//動作確認用、foo~fugaは無効なノード
NODES := [...]string{
"foo",
"bar",
"baz",
"piyo",
"fuga",
"alice7.nem.ninja:7890"}
rand.Seed(time.Now().UnixNano())
target_node := NODES[rand.Intn(len(NODES))]
endpoint := "http://" + target_node + "/heartbeat"
return endpoint
}
func get_heartbeat(result_c chan Heartbeat, err_c chan error) {
endpoint := get_endpoint()
resp, err := http.Get(endpoint)
if err != nil {
fmt.Println(err)
get_heartbeat(result_c, err_c)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
get_heartbeat(result_c, err_c)
return
}
var heartbeat Heartbeat
if err := json.Unmarshal(body, &heartbeat); err != nil {
fmt.Println(err)
get_heartbeat(result_c, err_c)
return
} else {
result_c <- heartbeat
}
}
func main() {
result_c := make(chan Heartbeat, 1)
err_c := make(chan error, 1)
go get_heartbeat(result_c, err_c)
select {
case result := <-result_c:
fmt.Println(result)
case err := <-err_c:
fmt.Println(err)
}
}
実行結果
Get http://baz/heartbeat: dial tcp: lookup baz: no such host
Get http://piyo/heartbeat: dial tcp: lookup piyo: no such host
Get http://fuga/heartbeat: dial tcp: lookup fuga: no such host
Get http://piyo/heartbeat: dial tcp: lookup piyo: no such host
Get http://foo/heartbeat: dial tcp: lookup foo: no such host
Get http://baz/heartbeat: dial tcp: lookup baz: no such host
Get http://baz/heartbeat: dial tcp: lookup baz: no such host
{1 2 ok}
解説
初めに紹介したソースコードに対し以下の変更がなされています。
- arrayからランダムにノードを取り出しendpointを取得するget_endpoint()の追加
- get_heartbeat()の変更
- get_endpoint()を利用
- 失敗時に再帰
実行結果から、ノードへの接続に失敗すると、ランダムにノードを選び直し、再度接続する処理を行っていることがわかります。