ls -al

仮想通貨やプログラミングに関する事などをつらつらと書き綴ります

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()を利用
    • 失敗時に再帰

実行結果から、ノードへの接続に失敗すると、ランダムにノードを選び直し、再度接続する処理を行っていることがわかります。

参考

Qiita - 仮想通貨NEMでJavaScriptによるノード接続に失敗した場合に別ノードを自動選択する。