20190522のGoに関する記事は10件です。

Go言語で折れ線グラフや棒グラフを描く

Goでグラフを描きたい

データを可視化するツールやパッケージは沢山ありますが,
Go言語のみでグラフを描く必要があったので,その際に調べたことを整理しました.

Overview

作業環境

  • Go 1.12
  • Windows 10
  • Goland 2019.1

事前準備

以下のコマンドで,goパッケージをインストールしてください.
こちらがgoでグラフを描画するために利用するパッケージになります.
パッケージの詳細はこちら

go get gonum.org/v1/plot/...

折れ線グラフ

折れ線グラフを描画するまでに必要なコードを説明します.
こちらがインポートするパッケージ一覧です.

import (
    "gonum.org/v1/plot"
    "gonum.org/v1/plot/plotter"
    "gonum.org/v1/plot/plotutil"
    "gonum.org/v1/plot/vg"
)

まずは,plotインスタンスを生成してください.
続いて,グラフの表示項目を設定します.

// インスタンスを生成
p, err := plot.New()
if err != nil {
    panic(err)
}

// 表示項目の設定
p.Title.Text = "only english title"
p.X.Label.Text = "X axis"
p.Y.Label.Text = "Y axis"

棒グラフの表示には plotter.XYs 型を利用します.
XY 型は文字通り X,Y軸の値を表現します.数学でよく使うx,y座標表現と対応しています.

type XYs []XY

type XY struct {
    X, Y float64
}

今回のためにサンプルデータを作成しました.
xyAxis構造体を定義し,そのスライスを作成しています.

type xyAxis struct {
    x float64
    y float64
}

nums := []xyAxis {
    [1, 11],
    [2, 33],
    [3, 22],
    [4, 55],
    [5, 44],
    [6, 77],
    [7, 66],
    [8, 99],
    [9, 88],
}

サンプルデータをplotter.XYs型に当てはめます.

pts := make(plotter.XYs, len(nums))

for i, axis := range nums {
    pts[i].X = axis.x
    pts[i].Y = axis.y
}

これでデータの準備は完了しました.
以下の関数を利用してグラフを生成し,その結果を保存します.

// グラフを描画
err = plotutil.AddLinePoints(p, pts)
if err != nil {
    panic(err)
}

// 描画結果を保存
// "5*vg.Inch" の数値を変更すれば,保存する画像のサイズを調整できます.
if err := p.Save(5*vg.Inch, 5*vg.Inch, "sample1.png"); err != nil{
    panic(err)
}

ファイルを実行したのと同じディレクトリに"sample1.png"が生成されます.

sample1.png
sample1.png

棒グラフ

棒グラフを描画するまでに必要なコードを説明します.
こちらがインポートするパッケージ一覧です.

import (
    "gonum.org/v1/plot"
    "gonum.org/v1/plot/plotter"
    "gonum.org/v1/plot/plotutil"
    "gonum.org/v1/plot/vg"
)

まずは,plotインスタンスを生成してください.
続いて,グラフの表示項目を設定します.

// インスタンスを生成
p, err := plot.New()
if err != nil {
    panic(err)
}

// 表示項目の設定
p.Title.Text = "only english title"
p.Y.Label.Text = "Y axis"

棒グラフの描画にはplotter.Values型を利用します.

type Values []float64

今回用意したサンプルデータはこちらです.

nums := plotter.Values{11,33,22,55,44,77,66,99,88}

棒グラフで描画する棒の「幅」を設定します.
今回は15ピクセルとします.

breadth := vg.Points(15)

棒グラフを生成します.
描画後に詳細を設定し,レイアウトを整理します.

bar, err := plotter.NewBarChart(num, breadth)
if err != nil {
    panic(err)
}

// 棒グラフ自体の枠線
bar.LineStyle.Width = vg.Length(0)

// 色を決める 0は赤になります.
bar.Color = plotutil.Color(0)

生成した棒グラフを描画します.
同時に,グラフの説明を追加します.

p.Add(bar)

// 棒グラフの名前を"bar1"とします.
p.Legend.Add("bar1", bar)

// legendの位置を上側にします(デフォルトは下側)
p.Legend.Top = true

// X軸側の説明項目を追加します.
p.NominalX("one", "two", "three", "four", "five", "six", "seven", "eight", "nine")

最後に,描画した画像を保存します.

if err := p.Save(5*vg.Inch, 5*vg.Inch, "sample2.png"); err != nil {
    panic(err)
}

sample2.png
sample2.png

参照記事

package plot
Example plots

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語の言語特性を学ぶ

はじめに

この記事は、Go言語を学ぼうとしているエンジニアが
Go言語の基語特性を学ぶことを目的としています。

私はGo言語の経験は無いのですが、事情により今後使用することになりそうですので、
どうせなら最初に言語特性を学びたいと思い、調べてみました。

Go言語の概要

gopher.jpg
Go言語とは、2009年にGoogleが公開したオープンソースのプログラミング言語です。
プログラミング言語研究のための言語ではなく、
問題解決にフォーカスしたプログラミング言語として世界的に高い人気があります。
規模の大きなプロジェクトに向いており、
システムやネットワーク、サーバーサイド、CUIツールでは定番言語の一つになりつつあり、
日本でのユーザー数も増えているそうです。

設計方針としては、既存の言語の良いところを取り入れ、必要性の低いところは除き、
より安全で効率よくプログラムを書くことを目標としております。
また、実行形式はコンパイラ型でして、構文はC言語が基になってます。

Go言語が解決したい問題

・コンパイル速度の遅さ
・実効速度の遅さ
・システム開発の遅さ
・依存関係の複雑さ
・扱いにくい型システム
・ガーベジコレクションがうまくサポートされていない
・並列処理がうまくサポートされていない
・冗長なソースの記述、繰り返し

Go言語の目標

・コンパイル速度の向上
・C/C++とほぼ同等の実行効率
・依存関係の解析を容易にする
・型間の関係性を定義する時間をなくす(静的型付け)
・完全なガーベジコレクション
・並列処理を言語としてサポート
・言語をシンプルに保つ

Go言語の特徴

・シンプルな言語仕様
  多くの言語は同じような機能追加競争を行い、似通った言語になりつつあります。
  その一方、Go言語は機能を削ることでシンプルさに徹底的に拘っています。
  コア開発者の全員が必要だと思った機能しか言語に取り込まないという方針が、
  シンプルさに寄与していると言えます。

・コンパイルが高速
  C言語系のコンパイラに比べ、コンパイル速度が速くなっています。
  C言語のようなインクルードファイルを採用しなかったことで、
  コンパイル時のオーバーヘッドが削減されました。

・並列処理を言語としてサポート
  並列処理を言語としてサポートしているため、複数の処理を同時に実行できます。
  この並列処理は「ゴルーチン」という実行単位で管理され、
  必要に応じてOSのスレッドに割り当てられて実行されます。
  (プログラマがプロセッサやスレッドを意識しなくてよい)

・ガーベジコレクション
  メモリの未使用領域が自動的に開放されます。
  なのでソースでのメモリ管理が不要になり、メモリリークを抑制できます。

・安全性が高い
  C言語と同じポインタがありますが、不正なメモリアクセスができないように、
  ポインタの演算はできません。
  また、型のチェックも厳密に行われるため、バグによってメモリが壊される危険性を回避しています。

・継承がない
  Javaのような一般的なオブジェクト指向言語で用いられる「クラス」がありません。
  よって、継承という概念がありません。
  その代わりに「埋め込み」や「インタフェース」とった仕組みが用意されています。

・例外がない
  Java等の多くの言語で採用されている「try~catch」による例外処理機構がありません。
  ならば、Go言語はどのようにエラーハンドリングをしているかというと、
  前提として、Go言語の関数は「複数個の値」を戻り値として返せます。
  なので、エラーハンドリングは必要に応じて
  関数の実行結果と一緒にエラー情報を返すことにより実現しています。

Go言語が使われているサービス

AbemaTV:国内最大の無料インターネットテレビ局
クックパッド:レシピ検索No.1/料理レシピ載せるなら クックパッド
Cacoo:フローチャートが作れるオンライン作図ツール
グノシー:無料で読めるニュースまとめ
BASE (ベイス):ネットショップを無料で簡単に作成

Go言語で簡単なプログラミング

■ぼくの開発環境
 ・言語:Go言語(1.12)
 ・IDE:Eclipse(4.11.0)
 ・プラグイン:GoClipse(0.16.1)

■基本知識
 ・拡張子は「.go」
 ・文字エンコードはUnicodeの「UTF-8」
 ・アルファベットの大文字と小文字は異なる識別子として扱われる
 ・何らかのパッケージに属している必要があり、1つは必ずmainパッケージでなければならない
 ・最初にmainパッケージ内のmain()関数が実行される
 ・関数宣言のような波括弧「{}」は、開始波括弧「{」を同じ行に書く必要がある
 ・タブ区切り推奨
 ・セミコロンなし推奨(1つの行に複数の文を記述する場合は明示しなければならない)

■サンプルソース&解説

// このソースファイルは「main」パッケージに属すことを指定しています。
// また、Go言語のソースファイルは必ず「package」文で始まります。
package main

// 標準ライブラリであるパッケージ「fmt」をインポートしています。
// 「fmt」はフォーマットI/Oを実装しており、C言語のprintfおよびscanfと似た関数を持ちます。
import "fmt"

// 「func」が関数の宣言で、ここから「main」関数の記述が始まります。
// ※関数宣言のような波括弧「{}」は、開始波括弧「{」をfuncと同じ行に書く必要があります。
func main() {

    // インポートした「fmt」パッケージ内の「Print」関数を使用し、標準出力に「Hello world!」と出力します。
    fmt.Printf("Hello world!\n")
}

参考にしたサイト

・Go言語の公式サイト:The Go Programming Language
・Go言語の公式サイトの日本語訳:プログラミング言語Goの情報サイト
・Go言語のfmtパッケージ仕様:パッケージドキュメント
・stackoverflow:Developer Survey Results 2019

おわりに

Go言語の設計思想や特徴について学ぶことができました。

調べていく中で、Go言語は設計思想が合理的で、
現代のニーズにマッチしている(下から目線)と感じました。
その理由は、Go言語は既存の言語の良いところを取り入れつつも、必要性の低いところは取り除き、
かつ、現代のシステム開発における問題を解決するための機能を提案し実現しようとしているという、
本当に問題解決にフォーカスしたプログラミング言語なのだなと思ったからです。

次回は、Go言語のアプリをGCPにデプロイするまでの流れについて投稿しようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像処理の基本的なアルゴリズムをgolangで復習 4(バイリニア補間)

画像処理の代表的なアルゴリズムをGO言語で実装しながら復習するシリーズです。

はじめに

前回に引き続き、画像の拡大縮小に用いられる線形補間について振り返ります。
今回はバイリニア補間(双一次補間)についてです。

バイリニア補間(双一次補間)

注目画素の周辺2×2画素、合計で4画素を使って、画素値を直線的に補間する手法です。
前回よりも周辺画素の影響も考えた処理になっています。
以下図は、画像を横方向4倍にバイリニアで拡大する時の簡単なイメージです。

img1.png

厳密には異なる表現もあるかもしれないですが、ざっくりこんな感じの理解で良いかと。

検証方法

以下手順で検証を行いました。

① 元画像をPhotoshopのバイキュービック法で1/2に縮小
② 縮小した元画像を今回実装したバイリニア補間で2倍に拡大
③ 元画像とバイリニア補間で2倍に拡大した画像を比較

ソースコード

Bilinear.go

package Bilinear

import (
    "fmt"
    "gocv.io/x/gocv"
)

func Init() {

}

// バイリニア法
func Execution(preImg gocv.Mat, inputImageRows int, inputImageCols int, scalingRows float64, scalingCols float64) gocv.Mat {

    // 重み値を定義
    var x float64
    var y float64
    // リサイズ後画像サイズ
    resizeImageRows := int(float64(inputImageRows) * scalingRows)
    resizeImageCols := int(float64(inputImageCols) * scalingCols)

    // 逆数
    reciprocalScalingRows := 1 / scalingRows
    reciprocalScalingCols := 1 / scalingCols

    // アウトプット画像を定義
    outputImg := gocv.NewMatWithSize(resizeImageRows, resizeImageCols, gocv.IMReadGrayScale)

    // 画像の左上から順に画素を読み込む
    for imgRows := 0; imgRows < resizeImageRows; imgRows++ {
        for imgCols := 0; imgCols < resizeImageCols; imgCols++ {

            // 双一次補完式

            // 元画像の座標定義
            // 元画像の縦の座標
            inputRows := int(float64(imgRows) / scalingRows)
            // 元画像の横の座標
            inputCols := int(float64(imgCols) / scalingCols)


            // 補完式で使う元画像のpixel
            // point(0, 0)
            src00 := float64(preImg.GetUCharAt(inputRows+inputImageRows, inputCols+inputImageCols))
            // point(0, 1)
            src01 := float64(preImg.GetUCharAt(inputRows+inputImageRows, inputCols+inputImageCols+1))
            // point(1, 0)
            src10 := float64(preImg.GetUCharAt(inputRows+inputImageRows+1, inputCols+inputImageCols))
            // point(1, 1)
            src11 := float64(preImg.GetUCharAt(inputRows+inputImageRows+1, inputCols+inputImageCols+1))

            // 重み値を算出
            x = float64(imgCols) * reciprocalScalingCols
            y = float64(imgRows) * reciprocalScalingRows
            // 小数点以下を抽出
            x = x - float64(int(x))
            y = y - float64(int(y))

            // 拡大後の画素を算出
            pixel1 := (1 - x) * (1 - y) * src00
            pixel2 := x * (1 - y) * src01
            pixel3 := (1 - x) * y * src10
            pixel4 := x * y * src11

            pixel := pixel1 + pixel2 + pixel3 + pixel4

            outputImg.SetUCharAt(imgRows, imgCols, uint8(pixel))
        }
    }

    return outputImg
}

実行結果

元画像

Lennagrey.png

実行後画像

Bili2.png

ニアレストネイバーと比べると、多少元画像に近づいているように見えます。
エッジ部分はバイリニア補間の方が滑らかになっています。

元画像と実行後画像の差分

Bili2_dis.png

元画像との差を見てみると、満遍なく差が出ているように見えます。
エッジが滑らかになっている影響でしょうか。

MSE/PSNR

[MSE] value :  +6.462402e+001 [PSNR] value :  +3.002686e+001

前回のニアレストネイバーよりPSNRが上がっています。
今回使った画像では、周辺画素の影響を考えることで、元画像との差分を減らすことが出来ることが分かります。

まとめ

バイリニア補間ではニアレストネイバーに比べ、エッジのギザギザが緩和されていました。
ただこの結果で、「バイリニアの方が優れている」となるよりかは、場合によって使い分けていくのが無難かと思います。処理する画像によっても性質は色々なので。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バックエンドエンジニアがReact(JS)+Gin(Golang)でwebサービスを作ってみた

webサービスを作ってみたのでその紹介と、作ってみた感想です。特定の技術に関する説明をする記事ではありません。

どんなサービスか

色の名前とそれに紐付くRGB値等の定義はHTMLJIS慣用色名などがありますが、仮に「赤」という色が定義上#ff0000だとしても、多くの人は#ff0101を見たときも「赤」だと認識すると思います。本来の定義からどれくらい外れていたらその色と認識しなくなるかは、人によって違いそうです。

これについて自分以外の人がどのように認識しているか知りたいと思いました。その方法としては、「赤」という色の名前とともに#ff0000に近い色を複数表示して、「赤」だと思うものを選択する、という作業を複数人にしてもらうことで、表示されたそれぞれの色についてどの割合でそれを「赤」だと思う人がいるか知ることができそうです。

この情報を収集してうまく表現できれば「これは赤では?」「うーん、違う気がする」といったシチュエーションに対して「ではどの割合でそう思う人がいるか調べてみよう?」という解決策をもらたすことができますよね?

これを実現することを目標にしたのが今回紹介するcolor-consensusというサービスになります。

以下が使い方の説明です。

  • まずアクセスすると以下のような画面が出てきます。右側のナビゲーションでGreenを選んでますから、画面中央のマス目からGreenだと思う色を選んでほしいという画面です。クリックやドラッグで選択できます。

  • 入力した情報をSubmitするにはログインが必要です。非ログイン状態でSubmitを押したり右上のそれっぽいボタンを押すとサインアップかログインができるモーダルが開きます。ユーザがログインIDとパスワードを指定する方式ではないです。ユーザ情報を入力してSubmitするとサービスから一方的にログインIDを発行してSet-Cookieします。そのログインIDは今まで右上に表示されていたサインアップ/ログインボタンの部分に表示され、クリックでクリップボードにコピーできます。

  • 色を選んだりログインしたりしたくない場合は灰色のGo to statistics →ボタンから統計情報を表示する画面に遷移できます。

  • 色を選んでSubmit →を押しても同様に統計情報を表示する画面に遷移します。その画面では中央上のフィルターに沿って選択された色が白い線で囲われます。何人の人がSubmit →したかもVote Countとして表示されています。

  • フィルターをいじることで人の属性を限定して結果を表示できます。現状日本人の僕しかSubmitしていないのでJapanしかないですね。

作ってみたサービスはこちらです。 → https://color-consensus.herokuapp.com/

ソースコードも公開しています。 → https://github.com/konohiroaki/color-consensus

コードの規模はテストを含めて7000行くらい。フロントエンドとバックエンドの割合はバックエンドの方が半分より少し多いくらいです。

開発期間は3~4ヶ月ほどで、なるべく毎日触るようにしながら、焦らず少しずつ進めていました。

技術スタック

仕事では Java8, Spring Framework, Cloud Foundary あたりを使ったwebのバックエンド開発をメインでしています。今回の開発を通してjQueryあたりで止まっているフロントの知識をアップデートしたいと思いました。

フロントエンド

Webpack, Babel, Javascript, React, Redux, bootstrap, など
- Webpack以外の選択肢もあるようでしたが、Webpackという名前をよく聞くのでそれを選ぶことにしました。
- VueとReactは悩みましたがReactのドキュメントを読み始めたらするする読めたのでそのままそれを使うことにしました。

バックエンド

Golang, Gin framework, MongoDB, など
- 仕事で使っている技術そのままでもできましたが、バックエンド開発でも新しいことを学ぶために最近流行りのGolangを使うことにしてみました。
- webフレームワークはGolang用のものでGithub上で一番Starのあるものを選びました。

プラットフォーム

Heroku
- ここで時間を使いたくなかったというのがVPSを借りるなどしなかった理由です。コードを書くことに集中したかったです。あとHerokuの一番下のプランだと無料なのも嬉しいです。

開発の流れ

だいたい以下のような流れで開発をしました。メモしながら開発していたわけではなく記憶を頼りに書いているので結構雑ですが。

準備
開発
  • 最初はReactの公式ドキュメントを読みながらreactのアプリを動くようにしました。最初の時点ではReduxは使っていません。
  • 同じ頃にバックエンドにも簡単なHTTPのエンドポイントを作ってみました。
  • 画面主導で開発を行い、表示する色を選択するための画面右側の部分 → ユーザ入力部分 → 統計情報表示部分 の順に作っていき、必要に応じてバックエンドのAPIも実装していきました。
    • ざっくりこのような部品を作るというのは初めの頃に決めていましたが、細かい部分は開発しながら気付いたときにTODOとしてコード中にメモを残して、頃合いを見て実装していきました。
  • 統計情報表示部分の開発をする頃にstatspropsだけでデータを引きずり回すのが苦しくなってきたので、Redux in Motionで使い方を覚えて導入しました。データを一元管理できるようになって格段に楽になったので、もっと早くやればよかったです。
  • 統計情報表示部分をデモグラフィックでフィルターできるようにしてみました。この機能でサインアップ時に入力した情報が使われています。
  • gomocktestifyを使ってテスト皆無だったバックエンドにテストを書きまくりました。
  • ようやくtravis CIを入れました。Githubにpushしたら自動でテストが流れてデプロイするようになりました。ここまで待たずに最初にやっても良かったように思います。
  • ここまで色の名前を言語(Japanese, Englishなど)に紐づけていましたが、言語というより色カテゴリ(HTML Color, JIS慣用色名など)という上位互換みたいな概念を使ったほうがいいと思ったので、ごっそり書き換えました。

サービスの課題

とりあえず動くものの、課題はまだまだあります。

サービスの外から見えてる部分の課題

  • ユーザが選択できる色の候補が微妙です。特にグレーっぽい色だと使い物になりません。つらい。
  • UI上で色候補を選ぶ作業のUXがだいぶ低いように感じます。僕でもだるいのに、誰がやるのって思います。かなり目が疲れます。
    • 一色ずつ表示してyes/noで答えてもらうみたいな感じのUIがいいかもしれません。ユーザのyes/noに応じて表示する色を賢く変えていくみたいにできれば結構良さそうな気がします。もう少しアイディアが具体的にできたらごっそり書き換えてみたいですね。うまくできれば上下の課題も同時に解決できそうです。
  • PCのGoogle Chromeでしか動作確認をしていません。
    • スマホで色を選択する部分が動かないことは確認しました?
  • Herokuの無料プランなので30分アクセスがないと次のアクセスを処理するのに数秒かかります。

サービスの中身の課題

  • ユーザが選択した色を全てDBに持っていますが、おそらく選んだ色より近い色(どう判断できるかは要定義ですね)は選んだとみなしていい気がします。そういう意味では保存してるデータが冗長な感じです。
  • Reactが賢いのでXSSなどはなさそうですが、悪意のあるポストへの対策が基本的にありません。適当なデータをpostしまくればちゃんとした情報を埋もれさせてしまえます。
  • 統計情報表示画面ではその色に対してSubmitされている情報を全て取得してUIを描画していますが、データ量が増えたらこれではダメそうです。フィルターを変更するごとに通信してサマリー情報を取れるようにしても良さそうです。
  • Reactのコンポーネント設計ではロジックを記述するWrapperコンポーネントとHTMLを出力するコンポーネントを分けるのがお作法のようです。今はそれらを適用したときに比べてメンテナンス性が低い状態だと思います。
  • Reduxのモジュールにはある程度テストを書いてますが、Reactコンポーネントのテストは書けていません。つまり目でチェックしてる?
    • 見た目の部分は他と比べて変化の多い部分でもあり、全部きっちりテストを書いたら逆にメンテナンスが大変そうです。やり方要検討ですね。

というわけで改善の余地はまだまだありすぎますね。

全体を通しての感想

  • Golangが流行っている割にあまり書きやすい感じはしませんでした。機能が少なくて古い言語な印象です。今回必要と思う部分がなくてGoroutineを使わなかったことがGolangを使うメリットを大きく減らしてしまったのかなと思います。
    • Goroutineは動作原理も独特のようですが、並行処理のコード上の表現がJavaで使えるFuture objectを用いたアプローチとかなり異なるので、それがコードの設計にどのように影響するのか見てみたいと思っています。
  • UI上の典型的な表現がreact-toastifyなどのパッケージで大体解決できるという体験が新鮮でした。色んなパッケージを試すのが楽しいです。
  • フロントエンドはバックエンドに比べてデバッグのためのツールが充実してるように感じました。redux-devtools-extensionとかスゴイ。
  • フロントエンドはJavascriptで開発しましたが、Typescriptや全然別の言語からもトランスパイルできる事を途中で知りました。次は静的型付けできる言語を選択してみたいです。
  • 開発中コードを追加するたびにちょくちょくリファクタリングをしていました。おかげで忘れたコードもわりとすぐ理解を取り戻せるという状態で進められました。結構忘れるので読みやすさは生命線です。
  • 散々言われてますが、やはり公式ドキュメントとか解説本とかを読むのがツールの理解に対して近道だと感じました。ツール名でググったときはツールの公式サイトが一番上に出てきてほしいです。
  • 可能な限り毎日触るようにしていたおかげで、モチベーションが上がったときだけやるのではなく、毎日コンスタントに何かをやるということが自分にとって自然になりました。これが一番の収穫だったように思います。

以上、作ったものの紹介と、作ってみた感想でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

何か変わった

サンプルを探す

  • ああでもない、こうでもないと試行錯誤しても上手くいかず
  • でも参考文献として読んでいた多くのブログに「動くコードをコピーしてまず動かす。そこから部分的に修正してやりたい事を実際に動かすのよい」要はコピペを駆使して動かしてみましょう、と書いてあると解釈
  • サーバーなりネットワーク機器だとパッケージをインストールした際にサンプルコンフィグがあって、それを修正して動作させる。その意識はあったが、プログラムってなんか手慣れた人達がさらさらさらさ~らっと書いて動かしているイメージがあった(個人的な印象です)

実際に探してみた

  • ちょっと検索しただけでもすんごく出てきた :disappointed_relieved:
  • 色々見ると自分には難しい。んでたどり着いたのが以下、これなら何となく分かった(気がする)

動かしてみた(コピペで)

$ go run http-client.org.go 
get:
 User-agent: *
Disallow: /search
Allow: /search/about
post:
 <!DOCTYPE html>
<!--[if IEMobile 7 ]> <html lang="en_US" class="no-js iem7"> <![endif]-->
<!--[if lt IE 7]> <html class="ie6 lt-ie10 lt-ie9 lt-ie8 lt-ie7 no-js" lang="en_US"> <![endif]-->
  • http.Getの部分をhttp://www.google.com/にしてみた -(あたりまえだけど)すんげ~出た
$ go run http-client.go 
get:
 <!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en"><head><meta content="Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for." name="description"><meta content="noodp" name="robots"><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"><meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png" itemprop="image"><title>Google</title><script nonce="SHnQTfU6Ib6WB7M7EohLwQ==">(function(){window.google={kEI:'iQzlXMfLNIKwsAWJjLeIDQ',kEXPI:'0,1353747,57,51,1907,1641,781,697,528,730,224,1575,1258,1894,56,321,207,1017,1187,162,2332255,329520,1294,12383,4855,32692,15247,867,12163,6381,3335,2,2,4609,2192,369,3314,5505,224,2218,260,5107,575,835,284,2,578,728,2432,58,2,1,3,1297,4323,3700,1268,773,2247,1410,3337,1146,5,2,2,1965,2593,3601,669,1050,1808,1397,81,7,1,2,488,620,29,1395,3610,3632,1667,1288,2,4007,796,101,1119,38,920,754,119,1217,1364,1611,2729,7,1558,1503,2,631,2562,2,4,2,678,37,369,1399,2889,124,2608,12,620,2228,655,17,321,236,1357,389,142,86,2,961,198,777,1,2,367,1016,301,704,756,98,36,2,366,18,399,259,733,509,598,10,168,8,109,187,831,235,810,450,904,155,82,48,459,94,11,14,10,1659,839,57,5,14,380,146,381,25,177,39,284,5,1252,66,91,141,542,92,1,231,164,3package main
0,530,267,11,93,112,125,1,35,25,217,10,39,18,22,16,158,1570,604,21,33,71,24,127,1,210,279,319,23,818,260,1187,1,3,7,7,1,2,53,1097,5,337,540,14,606,454,184,127,13,184,6,333,14,195,1,320,7,185,44,324,204,24,1081,242,15,363,24,275,58,4,128,1,12,104,61,599,12,1,30,144,1,4,1,1,4,1,1,479,119,130,34,782,340,767,73,4,29,314,1204,115,5931739,2885,5997591,2799864,4,1572,549,333,444,1,2,80,1,900,583,7,306,1,8,1,2,2132,1,1,1,1,1,414,1,748,141,59,726,3,7,563,1,2075,72,44,2,4,31,1,1,8,10,1,5,5,20747724,1557155',authuser:0,kscs:'c9c918f0_iQzlXMfLNIKwsAWJjLeIDQ',kGL:'US'};google.sn='webhp';google.kHL='en';})();(function(){google.lc=[];google.li=0;google.getEI=function(a){for(var b;a&&(!a.getAttribute||!(b=a.getAttribute("eid")));)a=a.parentNode;return b||google.kEI};google.getLEI=function(a){for(var b=null;a&&(!a.getAttribute||!(b=a.getAttribute("leid")));)a=a.parentNode;return b};google.https=function(){return"https:"==window.location.protocol};google.ml=function(){return null};google.time=function(){return(new Date).getTime()};google.log=function(a,b,e,c,g){if(a=google.logUrl(a,b,e,c,g)){b=new Image;var d=google.lc,f=google.li;d[f]=b;b.onerror=b.onload=b.onabort=function(){delete d[f]};google.vel&&google.vel.lu&&google.vel.lu(a);b.src=a;google.li=f+1}};google.logUrl=function(a,b,e,c,g){var d="",f=google.ls||"";e||-1!=b.search("&ei=")||(d="&ei="+google.getEI(c),-1==b.search("&lei=")&&(c=google.getLEI(c))&&(d+="&lei="+c));c="";!e&&google.cshid&&-1==b.search("&cshid=")&&"slh"!=a&&(c="&cshid="+google.cshid);a=e||"/"+(g||"gen_204")+"?atyp=i&ct="+a+"&cad="+b+d+f+"&zx="+google.time()+c;/^http:/i.test(a)&&google.https()&&(google.ml(Error("a"),!1,{src:a,glmm:1}),a="");return a};}).call(this);(function(){google.y={};google.x=function(a,b){if(a)var c=a.id;else{do c=Math.random();while(google.y[c])}google.y[c]=[a,b];return!1};google.lm=[];google.plm=function(a){google.lm.push.apply(google.lm,a)};google.package main
lq=[];google.load=function(a,b,c){google.lq.push([[a],b,c])};google.loadAll=function(a,b){google.lq.push([a,b])};}).call(this);google.f={};var a=window.location,b=a.href.indexOf("#");if(0<=b){var c=a.href.substring(b+1);/(^|&)q=/.test(c)&&-1==c.indexOf("#")&&a.replace("/search?"+c.replace(/(^|&)fp=[^&]*/g,"")+"&cad=h")};</script><style>#gbar,#guser{font-size:13px;padding-top:1px !important;}#gbar{height:22px}#guser{padding-bottom:7px !important;text-align:right}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolute;top:24px;width:100%}@media all{.gb1{height:22px;margin-right:.5em;vertical-align:top}#gbar{float:left}}a.gb1,a.gb4{text-decoration:underline !important}a.gb1,a.gb4{color:#00c !important}.gbi .gb4{color:#dd8e27 !important}.gbf .gb4{color:#900 !important}
</style><style>body,td,a,p,.h{font-family:arial,sans-serif}body{margin:0;overflow-y:scroll}#gog{padding:3px 8px 0}td{line-height:.8em}.gac_m td{line-height:17px}form{margin-bottom:20px}.h{color:#36c}.q{color:#00c}.ts td{padding:0}.ts{border-collapse:collapse}em{font-weight:bold;font-style:normal}.lst{height:25px;width:496px}.gsfi,.lst{font:18px arial,sans-serif}.gsfs{font:17px arial,sans-serif}.ds{display:inline-box;display:inline-block;margin:3px 0 4px;margin-left:4px}input{font-family:inherit}a.gb1,a.gb2,a.gb3,a.gb4{color:#11c !important}body{background:#fff;color:black}a{color:#11c;text-decoration:none}a:hover,a:active{text-decoration:underline}.fl a{color:#36c}a:visited{color:#551a8b}a.gb1,a.gb4{text-decoration:underline}a.gb3:hover{text-decoration:none}#ghead a.gb2:hover{color:#fff !important}.sblc{padding-top:5px}.sblc a{display:block;margin:2px 0;margin-left:13px;font-size:11px}.lsbb{background:#eee;border:solid 1px;border-color:#ccc package main
#999 #999 #ccc;height:30px}.lsbb{display:block}.ftl,#fll a{display:inline-block;margin:0 12px}.lsb{background:url(/images/nav_logo229.png) 0 -261px repeat-x;border:none;color:#000;cursor:pointer;height:30px;margin:0;outline:0;font:15px arial,sans-serif;vertical-align:top}.lsb:active{background:#ccc}.lst:focus{outline:none}</style><script nonce="SHnQTfU6Ib6WB7M7EohLwQ=="></script></head><body bgcolor="#fff"><script nonce="SHnQTfU6Ib6WB7M7EohLwQ==">(function(){var src='/images/nav_logo229.png';var iesg=false;document.body.onload = function(){window.n && window.n();if (document.images){new Image().src=src;}
if (!iesg){document.f&&document.f.q.focus();document.gbqf&&document.gbqf.q.focus();}
post:
 <!DOCTYPE html>
<!--[if IEMobile 7 ]> <html lang="en_US" class="no-js iem7"> <![endif]-->
<!--[if lt IE 7]> <html class="ie6 lt-ie10 lt-ie9 lt-ie8 lt-ie7 no-js" lang="en_US"> <![endif]-->

再挑戦

  • 前日のエラーとは変化があった為、http.Getの部分をAPIを実施するURLに変えてみた
  • 結果はダメ:frowning2:
$ go run ******_api.go 
panic: runtime error: slice bounds out of range

goroutine 1 [running]:
main.keepLines(0xc000281400, 0x3c4, 0x3, 0x600, 0xc000281400)
        /home/******/go_sandbox/******_api.go:13 +0x10b
main.main()
        /home/******/go_sandbox/******_api.go:25 +0x141
exit status 2
  • 表示されているエラーruntime error: slice bounds out of rangeで検索すると色々と例が事例がある様子
  • 動きはしなかったが、昨日よりは変化があったので一歩前進としよう。エラー内容はまたあとで見ます
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go net/http server snippet

最小

package main

import (
    "net/http"
    "log"
    "fmt"
)

func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "hello")
}

func main() {
    http.HandleFunc("/", hello)

    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

最小 (mux)

package main

import (
    "fmt"
    "log"
    "net/http"
)

func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "hello")
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", hello)

    if err := http.ListenAndServe(":8080", mux); err != nil {
        log.Fatal(err)
    }
}

github.com/gorilla/mux

package main

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

    "github.com/gorilla/mux"
)

func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "hello")
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/", hello)

    if err := http.ListenAndServe(":8080", r); err != nil {
        log.Fatal(err)
    }
}

github.com/gorilla/mux Method指定

package main

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

    "github.com/gorilla/mux"
)

func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "hello")
}

func main() {
    r := mux.NewRouter()
    r.Methods("GET").Path("/").HandlerFunc(hello)

    if err := http.ListenAndServe(":8080", r); err != nil {
        log.Fatal(err)
    }
}

query

package main

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

    "github.com/gorilla/mux"
)

func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "hello\n")
    q := r.URL.Query()
    if name, ok := q["name"]; ok {
        fmt.Fprintf(w, name[0])
    }
}

func main() {
    r := mux.NewRouter()
    r.Methods("GET").Path("/").HandlerFunc(hello)

    if err := http.ListenAndServe(":8080", r); err != nil {
        log.Fatal(err)
    }
}

POST form

package main

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

    "github.com/gorilla/mux"
)

func postHello(w http.ResponseWriter, r *http.Request) {
    name := r.FormValue("name")
    fmt.Fprintf(w, "hello, %v", name)
}

func main() {
    r := mux.NewRouter()
    r.Methods("POST").Path("/").HandlerFunc(postHello)

    if err := http.ListenAndServe(":8080", r); err != nil {
        log.Fatal(err)
    }
}

POST json

package main

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

    "github.com/gorilla/mux"
)

type Hello struct {
    Name string `json="name"`
}

func parseJsonRequest(w http.ResponseWriter, r *http.Request, data interface{}) (int, error) {
    //Validate request
    if r.Method != "POST" {
        return http.StatusBadRequest, fmt.Errorf("Bad method")
    }

    if r.Header.Get("Content-Type") != "application/json" {
        return http.StatusBadRequest, fmt.Errorf("Bad content type")
    }

    //To allocate slice for request body
    length, err := strconv.Atoi(r.Header.Get("Content-Length"))
    if err != nil {
        return http.StatusInternalServerError, err
    }

    //Read body data to parse json
    body := make([]byte, length)
    length, err = r.Body.Read(body)
    if err != nil && err != io.EOF {
        return http.StatusInternalServerError, err      
    }

    //parse json
    err = json.Unmarshal(body[:length], data)
    if err != nil {
        return http.StatusInternalServerError, err      
    }
    return http.StatusOK, nil
}

func postHello(w http.ResponseWriter, r *http.Request) {
    data := Hello{}
    status, err := parseJsonRequest(w, r, &data)
    if err != nil {
        w.WriteHeader(status)
        return
    }
    w.WriteHeader(status)

    encoder := json.NewEncoder(w)
    encoder.Encode(data)
}

func main() {
    r := mux.NewRouter()
    r.Methods("POST").Path("/").HandlerFunc(postHello)

    if err := http.ListenAndServe(":8080", r); err != nil {
        log.Fatal(err)
    }
}

Handler (ServeHTTP)

package main

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

    "github.com/gorilla/mux"
)

type Greeting struct {
    message string
}

func (g *Greeting) ServeHTTP (w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, g.message)
}

func main() {
    hello := Greeting{message: "hello"}
    r := mux.NewRouter()
    r.Methods("GET").Path("/").Handler(&hello)

    if err := http.ListenAndServe(":8080", r); err != nil {
        log.Fatal(err)
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gRPC

install go lang

$ wget https://storage.googleapis.com/golang/go1.2.5.linux-amd64.tar.gz
$ tar vzfx go1.12.5.linux-amd64.tar.gz
$ sudo mv go /usr/local/
$ export PATH=$PATH:/usr/local/go/bin
$ export GOPATH=$HOME/go
$ go version

test

$ mkdir go/src/hello
$ cd go/src/hello
$ touch hello.go
hello.go
package main

import "fmt"

func main() {
    fmt.Printf("hello, world\n")
}
$ go build
$ go run hello

gRPC

gRPCについて

totorials

hoge.proto

記述 記述ルール 説明
ファイル名 スネークケース メッセージとサービスを定義するファイル
メッセージ名 アッパーキャメルケース サーバーとクライアント間でやりとりするデータ
フィールド名 スネークケース データのフィールド名
サービス名 アッパーキャメルケース サーバーとクライアント間で通信するサービス名を定義する。リクエストとレスポンスのメッセージも記述。

公式ガイド

hoge_client/main.go
プロトコルファイルで定義したサービスやメッセージを使用してクライアントサイドサービスの振舞いを実装する。

記述 記述ルール 説明
サービス名 アッパーキャメルケース 振舞いを記述
メッセージフィールド アッパーキャメルケース データのフィールド名

hoge_server/main.go
プロトコルファイルで定義したサービスやメッセージを使用してサーバーサイドサービスの振舞いを実装する。

記述 記述ルール 説明
サービス名 アッパーキャメルケース 振舞いを記述
メッセージフィールド アッパーキャメルケース データのフィールド名

install gRPC

$ go get -u google.golang.org/grpc

Install Protocol Buffers v3

$ go get -u github.com/golang/protobuf/protoc-gen-go
$ export PATH=$PATH:$GOPATH/bin

examples
公式ドキュメント

Go言語に変換(対応言語のProtocol Buffersへ変換)

$ protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld

-bash: protoc: command not foundの場合

$ yum install zip
$ yum install unzip
$ PROTOC_ZIP=protoc-3.7.1-linux-x86_64.zip
$ curl -OL https://github.com/google/protobuf/releases/download/v3.7.1/$PROTOC_ZIP
$ sudo unzip -o $PROTOC_ZIP -d /usr/local bin/protoc
$ rm -f $PROTOC_ZIP

注意
proto、server、clientの順にコードを修正する。

お試し

helloworldを参考にさわってみる。

パッケージの準備
src/
 ├ user/
 │ └ user/
 │   └ user.proto
 ├ user_client/
 │   └ main.go
 ├ user_server/
 │   └ main.go
 └ mock_user/

protoファイル編集

user/user.proto
syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.user";
option java_outer_classname = "UserProto";

package user;

// User service definition.
service User {
  // send user_id
  rpc GetUser (RequestUserId) returns (UserResponse) {}
  // create user
  rpc CreateUser (CreateUserRequest) returns (UserResponse) {}

}

// The request message containing the user's name.
message RequestUserId {
  string user_id = 1;
}

message CreateUserRequest {
  string name = 1;
  string mail = 2;
  BloodType blood_type = 3;
  int32 age = 4;
}

message UserResponse {
  string id = 1;
  string name = 2;
  string mail = 3;
  BloodType blood_type = 4;
  int32 age = 5;
}

enum BloodType {
  A = 0;
  B = 1;
  O = 2;
  AB = 4;
}

Go言語へコンパイル

$ protoc -I user/ user/user.proto --go_out=plugins=grpc:user

serverファイル編集

user_server/main.go
//go:generate protoc -I ../users --go_out=plugins=grpc:../user ../user/user.proto

// Package main implements a server for User service.
package main

import (
        "context"
        "log"
        "net"

        "google.golang.org/grpc"
        pb "../user"
)

const (
        port = ":50051"
)

// server is used to implement Users.UsersServer.
type server struct{}

// GetUser implements User.UserServer
func (s *server) GetUser(ctx context.Context, user *pb.RequestUserId) (*pb.UserResponse, error) {
        log.Printf("Received: %v", user.UserId)
        return &pb.UserResponse{
          Id: user.UserId,
          Name:"Tom",
          Mail:"hoge@fuga.com",
          BloodType:0,
          Age:25}, nil
}

// CreateUser implements User.UserServer
func (s *server) CreateUser(ctx context.Context, m *pb.CreateUserRequest) (*pb.UserResponse, error) {
        return &pb.UserResponse{
          Id: "1",
          Name:m.Name,
          Mail:m.Mail,
          BloodType:m.BloodType,
          Age:m.Age}, nil
}

func main() {
        lis, err := net.Listen("tcp", port)
        if err != nil {
                log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer()
        pb.RegisterUserServer(s, &server{})
        if err := s.Serve(lis); err != nil {
                log.Fatalf("failed to serve: %v", err)
        }
}


clientファイル編集

user_client/main.go
// Package main implements a client for User service.
package main

import (
        "context"
        "log"
        "os"
        "time"

        "google.golang.org/grpc"
        pb "../user"
)

const (
        address     = "localhost:50051"
        defaultUserId = "1"
        createName = "Aoki"
        createMail = "hogehoge@example.com"
        createAge = int32(25)
)

func main() {
        // Set up a connection to the server.
        conn, err := grpc.Dial(address, grpc.WithInsecure())
        if err != nil {
                log.Fatalf("did not connect: %v", err)
        }
        defer conn.Close()
        c := pb.NewUserClient(conn)

        // Contact the server and print out its response.
        userId := defaultUserId
        if len(os.Args) > 1 {
                userId = os.Args[1]
        }
        ctx, cancel := context.WithTimeout(context.Background(), time.Second)
        defer cancel()
        r, err := c.GetUser(ctx, &pb.RequestUserId{UserId: userId})
        if err != nil {
                log.Fatalf("could not greet: %v", err)
        }
        log.Printf("Greeting: %s", r.Name)



        cName := createName
        cMail := createMail
        cAge := createAge
        r, err = c.CreateUser(ctx, &pb.CreateUserRequest{Name: cName, Mail: cMail, BloodType:0, Age: cAge})
        if err != nil {
                log.Fatalf("could not greet: %v", err)
        }
        log.Printf("Greeting: %s", r.Id)
}

gRPC server start

# 環境変数の登録してなかった場合は環境変数を設定
$ export PATH=$PATH:/usr/local/go/bin
$ export GOPATH=$HOME/go
$ export PATH=$PATH:$GOPATH/bin

$ go run users_server/main.go

gRPC client通信

# 環境変数の登録してなかった場合は環境変数を設定
$ export PATH=$PATH:/usr/local/go/bin
$ export GOPATH=$HOME/go
$ export PATH=$PATH:$GOPATH/bin

$ go run users_client/main.go
2019/05/21 08:08:17 Greeting: User Name: Tom
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gRPC触ってみた

install go lang

$ wget https://storage.googleapis.com/golang/go1.2.5.linux-amd64.tar.gz
$ tar vzfx go1.12.5.linux-amd64.tar.gz
$ sudo mv go /usr/local/
$ export PATH=$PATH:/usr/local/go/bin
$ export GOPATH=$HOME/go
$ go version

test

$ mkdir go/src/hello
$ cd go/src/hello
$ touch hello.go
hello.go
package main

import "fmt"

func main() {
    fmt.Printf("hello, world\n")
}
$ go build
$ go run hello

gRPC

gRPCについて

totorials

hoge.proto

記述 記述ルール 説明
ファイル名 スネークケース メッセージとサービスを定義するファイル
メッセージ名 アッパーキャメルケース サーバーとクライアント間でやりとりするデータ
フィールド名 スネークケース データのフィールド名
サービス名 アッパーキャメルケース サーバーとクライアント間で通信するサービス名を定義する。リクエストとレスポンスのメッセージも記述。

公式ガイド

hoge_client/main.go
プロトコルファイルで定義したサービスやメッセージを使用してクライアントサイドサービスの振舞いを実装する。

記述 記述ルール 説明
サービス名 アッパーキャメルケース 振舞いを記述
メッセージフィールド アッパーキャメルケース データのフィールド名

hoge_server/main.go
プロトコルファイルで定義したサービスやメッセージを使用してサーバーサイドサービスの振舞いを実装する。

記述 記述ルール 説明
サービス名 アッパーキャメルケース 振舞いを記述
メッセージフィールド アッパーキャメルケース データのフィールド名

install gRPC

$ go get -u google.golang.org/grpc

Install Protocol Buffers v3

$ go get -u github.com/golang/protobuf/protoc-gen-go
$ export PATH=$PATH:$GOPATH/bin

examples
公式ドキュメント

Go言語に変換(対応言語のProtocol Buffersへ変換)

$ protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld

-bash: protoc: command not foundの場合

$ yum install zip
$ yum install unzip
$ PROTOC_ZIP=protoc-3.7.1-linux-x86_64.zip
$ curl -OL https://github.com/google/protobuf/releases/download/v3.7.1/$PROTOC_ZIP
$ sudo unzip -o $PROTOC_ZIP -d /usr/local bin/protoc
$ rm -f $PROTOC_ZIP

注意
proto、server、clientの順にコードを修正する。

お試し

helloworldを参考にさわってみる。

パッケージの準備
src/
 ├ user/
 │ └ user/
 │   └ user.proto
 ├ user_client/
 │   └ get_user.go
 │   └ create_user.go
 ├ user_server/
 │   └ main.go
 └ mock_user/

protoファイル編集

user/user.proto
syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.user";
option java_outer_classname = "UserProto";

package user;

// User service definition.
service User {
  // send user_id
  rpc GetUser (RequestUserId) returns (UserResponse) {}
  // create user
  rpc CreateUser (CreateUserRequest) returns (UserResponse) {}

}

// The request message containing the user's name.
message RequestUserId {
  string user_id = 1;
}

message CreateUserRequest {
  string name = 1;
  string mail = 2;
  BloodType blood_type = 3;
  int32 age = 4;
}

message UserResponse {
  string id = 1;
  string name = 2;
  string mail = 3;
  BloodType blood_type = 4;
  int32 age = 5;
}

enum BloodType {
  A = 0;
  B = 1;
  O = 2;
  AB = 4;
}

Go言語へコンパイル

$ protoc -I user/ user/user.proto --go_out=plugins=grpc:user

serverファイル編集

user_server/main.go
//go:generate protoc -I ../users --go_out=plugins=grpc:../user ../user/user.proto

// Package main implements a server for User service.
package main

import (
        "context"
        "log"
        "net"

        "google.golang.org/grpc"
        pb "../user"
)

const (
        port = ":50051"
)

// server is used to implement Users.UsersServer.
type server struct{}

// GetUser implements User.UserServer
func (s *server) GetUser(ctx context.Context, user *pb.RequestUserId) (*pb.UserResponse, error) {
        log.Printf("Received: %v", user.UserId)
        return &pb.UserResponse{
          Id: user.UserId,
          Name:"Tom",
          Mail:"hoge@fuga.com",
          BloodType:0,
          Age:25}, nil
}

// CreateUser implements User.UserServer
func (s *server) CreateUser(ctx context.Context, m *pb.CreateUserRequest) (*pb.UserResponse, error) {
        return &pb.UserResponse{
          Id: "1",
          Name:m.Name,
          Mail:m.Mail,
          BloodType:m.BloodType,
          Age:m.Age}, nil
}

func main() {
        lis, err := net.Listen("tcp", port)
        if err != nil {
                log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer()
        pb.RegisterUserServer(s, &server{})
        if err := s.Serve(lis); err != nil {
                log.Fatalf("failed to serve: %v", err)
        }
}


clientファイル編集

user_client/get_user.go
// Package main implements a client for User service.
package main

import (
        "context"
        "log"
        "os"
        "time"

        "google.golang.org/grpc"
        pb "../user"
)

const (
        address     = "localhost:50051"
        defaultUserId = "1"
)

func main() {
        // Set up a connection to the server.
        conn, err := grpc.Dial(address, grpc.WithInsecure())
        if err != nil {
                log.Fatalf("did not connect: %v", err)
        }
        defer conn.Close()
        c := pb.NewUserClient(conn)

        // Contact the server and print out its response.
        userId := defaultUserId
        if len(os.Args) > 1 {
                userId = os.Args[1]
        }
        ctx, cancel := context.WithTimeout(context.Background(), time.Second)
        defer cancel()
        r, err := c.GetUser(ctx, &pb.RequestUserId{UserId: userId})
        if err != nil {
                log.Fatalf("get user error: %v", err)
        }
        log.Printf("User Name: %s", r.Name)
}
user_client/create_user.go
// Package main implements a client for User service.
package main

import (
        "context"
        "log"
        "time"
        "flag"

        "google.golang.org/grpc"
        pb "../user"
)

const (
        address     = "localhost:50051"
)

func main() {
        // Set up a connection to the server.
        conn, err := grpc.Dial(address, grpc.WithInsecure())
        if err != nil {
                log.Fatalf("did not connect: %v", err)
        }
        defer conn.Close()
        c := pb.NewUserClient(conn)

        ctx, cancel := context.WithTimeout(context.Background(), time.Second)
        defer cancel()

        var (
             cName = flag.String("name", "test", "name")
             cMail = flag.String("mail", "test@example.com", "mail")
             cAge = flag.Int("age", 20, "age")
        )
        flag.Parse()

        r, err := c.CreateUser(ctx, &pb.CreateUserRequest{Name: *cName, Mail: *cMail, BloodType:0, Age:int32(*cAge)})
        if err != nil {
                log.Fatalf("could not create user: %v", err)
        }
        log.Printf("Create User: {Id: %s, Name:%s, Mail:%s, BloodType:%s, Age:%s}", r.Id, r.Name, r.Mail, r.BloodType, r.Age)
}

gRPC server start

# 環境変数の登録してなかった場合は環境変数を設定
$ export PATH=$PATH:/usr/local/go/bin
$ export GOPATH=$HOME/go
$ export PATH=$PATH:$GOPATH/bin

$ go run users_server/main.go

gRPC client通信

# 環境変数の登録してなかった場合は環境変数を設定
$ export PATH=$PATH:/usr/local/go/bin
$ export GOPATH=$HOME/go
$ export PATH=$PATH:$GOPATH/bin

$ go run users_client/get_user.go
2019/05/22 06:10:50 User Name: Tom

$ go run user_client/create_user.go -name Thomas -mail test -age 25
2019/05/22 06:10:04 Create User: {Id: 1, Name:Thomas, Mail:test, BloodType:A, Age:%!s(int32=25)}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go+Postgres環境をDockerComposeで作ってみる

前回、Goの環境を作りました。
Webサーバーらしく、DBアクセスとかをする前に、DockerComposeで環境を整えようかと思います。

環境

  • ホストOS:Windows + VirtualBOX + Vagrant
  • ゲストOS:CentOS7+Docker

ディレクトリ構成

ゲストOSのディレクトリ構成は以下の通り

  • ~/docker/
    • docker-compose.yml
    • Dockerfile
    • main.go → 前回作成したもの

各種ファイル

docker-compose.yml
version: '2'
services:
  go-web:
    build: .
    ports:
      - "8080:8080"
    links:
      - go-db
    working_dir: /develop/go
    environment:
      - "DB_NAME=postgres"
      - "DB_USER=postgres"
      - "DB_PASSWORD=postgres"
    volumes:
      - "./:/develop/go:z"

  go-db:
    image: postgres:latest
    ports:
      - "5432:5432"
    environment:
      - "POSTGRES_USER=postgres"
      - "POSTGRES_PASSWORD=postgres"
    volumes:
      - "/data/db:/var/lib/postgresql/data:z"
FROM golang:latest

RUN go get github.com/gin-gonic/gin

EXPOSE 8080

CMD ["go", "run", "main.go"]

立ち上げてみる

  • 前回作成したmain.goを上記の2ファイルと同じ場所に格納
  • docker-compose upを実行

image.png
→立ち上がったので、ブラウザでアクセス

image.png
→アクセスできました

image.png
→サーバー側のログも出ています

最後に

今回、Postgresコンテナを立ち上げてはいるが、使っていないため今後はWebサーバーらしくDBアクセス等のコードも書いてみようと思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goのinterfaceがわからない人へ

はじめに

Go言語を勉強していて、interfaceが絡んでくるとなかなか理解ができなくなってしまうという人に読んでほしいです。
特にTour of GoStringersあたりでわからなくなった場合を想定しています。
また、Javaと対比して理解を深めることを目的としているため、Javaの経験がある方には刺さる説明になるかと思います。

なぜわからなくなるのか

原因としては2つあると思います。

  • そもそもインターフェースの概念がうまく理解できてない
  • 明示的な実装(implement)が前提になっていると勘違いしている

前者の方は、全く種類の違う型(クラス)がインターフェース型として、一部同じものとしてに扱うことができるということを知らない。
後者の方は、Javaの明示的なインターフェースの実装ありきで考えすぎて、Goの書き方に困惑してしまっている。
ということなのだと思います。

interfaceの実装例(お題:食べるということとは?)

eating.go
package main  

import "fmt" 

// 食べるためのインターフェース
type Eater interface{
    PutIn() // 口に入れる
    Chew() // 噛む
    Swallow() // 飲み込む
}
// 人間の構造体
type Human struct{
    Height int // 身長
}
// カメの構造体
type Turtle struct{
    Kind string // 種類
}

// 人間用のインターフェースの実装
func (h Human) PutIn(){
    fmt.Println("道具を使って丁寧に口に運ぶ")
}
func (h Human) Chew(){
    fmt.Println("歯でしっかり噛む")
}
func (h Human) Swallow(){
    fmt.Println("よく噛んだら飲み込む")
}

// カメ用のインターフェースの実装
func (h Turtle) PutIn(){
    fmt.Println("獲物を見つけたら首をすばやく伸ばして噛む")
}
func (h Turtle) Chew(){
    fmt.Println("クチバシで噛み砕く")
}
func (h Turtle) Swallow(){
    fmt.Println("小さく砕いたら飲み込む")
}

// インターフェースが引数になる、食べるメソッド
func EatAll(e Eatable){
    e.PutIn() // インターフェースから呼び出す
    e.Chew()
    e.Swallow()
}

func main() {
    var man Human = Human{Height: 300} // 人間用の構造体を作成
    var cheloniaMydas = Turtle{Kind: "アオウミガメ"} // カメ用の構造体を作成
    var eat Eater // インターフェースEatable型の変数を用意
    fmt.Println("<人間が食べる>")
    eat = man // Human型がインターフェースであるEatable型に変換される
    EatAll(eat) // インターフェースを引数に関数を呼ぶ
    fmt.Println("<カメが食べる>")
    eat = cheloniaMydas // Turtle型がインターフェースであるEatable型に変換される
    EatAll(eat)
}
Console
<人間が食べる>
道具を使って丁寧に口に運ぶ
歯でしっかり噛む
よく噛んだら飲み込む
<カメが食べる>
獲物を見つけたら首をすばやく伸ばして噛む
クチバシで噛み砕く
小さく砕いたら飲み込む

コードがずいぶんと長くなってしまいましたが、きちんと説明するのでついてきてください。
それでは、次の4つのフェーズに分けて紹介していきます。

  • インターフェースの定義
  • インターフェースを使った関数
  • インターフェースの実装
  • インターフェースの使い方

インターフェースの定義

// 食べるためのインターフェース
type Eater interface{
    PutIn() // 口に入れる
    Chew() // 噛む
    Swallow() // 飲み込む
}
// 人間の構造体
type Human struct{
    Height int // 身長
}
// カメの構造体
type Turtle struct{
    Kind string // 種類
}

まず「食べる」ということをプログラムで表現することを考えましょう。
口に入れる、噛む、飲み込むといったフェーズにわけて、関数をインターフェースに定義してみました。
といっても答えは人それぞれですが、今回はこんな感じで作ってみます。

次に、構造体(Javaでいうところのクラスに相当)を定義しました。
人間の構造体とカメの構造体です。ポイントは「それぞれの構造体の中身がまるっきり違ってもいい」ということにあります。

インターフェースを使った関数

// インターフェースが引数になる、食べるメソッド
func EatAll(e Eatable){
    e.PutIn() // インターフェースから呼び出す
    e.Chew()
    e.Swallow()
}

今回の最終ゴールは、「インターフェースを引数にとるEatAll関数を作ること」と考えるとわかりやすくなります。
まずその第一歩として、最初にインターフェースを定義したのです。

インターフェースを引数にとるとどんなことがうれしいのでしょうか。
例えば、こうすることで、「飲む」ということをプログラムで表現するときに、さきほどのインターフェースを使い回すことができそうですよね。液体なので噛む必要はなくなります。

// インターフェースが引数になる、飲むメソッド
func DrinkAll(e Eatable){
    e.PutIn() // インターフェースから呼び出す
    e.Swallow()
}

プログラムで書くとこんな感じです。

インターフェースの実装

私が一番混乱したのはここの部分です。
Javaであれば、

class Human implements Eatable{
    private int height;
    public void PutIn(){
        System.out.println("道具を使って・・・");
    }
    // ...
}

こうなるはずです。
決定的にJavaと違う点は、implementsがないことです。ここに注意してください。
Goでは、interfaceの中にある関数名と同一の関数が全て実装されている構造体に自動的に実装されると思ってください。

// 人間用のインターフェースの実装
func (h Human) PutIn(){
    fmt.Println("道具を使って丁寧に口に運ぶ")
}
func (h Human) Chew(){
    fmt.Println("歯でしっかり噛む")
}
func (h Human) Swallow(){
    fmt.Println("よく噛んだら飲み込む")
}

// カメ用のインターフェースの実装
func (h Turtle) PutIn(){
    fmt.Println("獲物を見つけたら首をすばやく伸ばして噛む")
}
func (h Turtle) Chew(){
    fmt.Println("クチバシで噛み砕く")
}
func (h Turtle) Swallow(){
    fmt.Println("小さく砕いたら飲み込む")
}

この部分では、レシーバーを使って、HumanTurtle両方にそれぞれの3つのメソッドを定義しています。
試しに、eating.goからHumanSwallow()を抜くとどうなるでしょうか。

source_file.go:56: cannot use man (type Human) as type Eater in assignment:
    Human does not implement Eater (missing Swallow method)

こうなります。
まだmain関数の中身を説明していないので、わかりづらいかもしれません。
これは、Human型がインターフェースのEatable型として割り当てることには使えないと言っています。
つまり、Human型にはインターフェースのEatable型が実装されていないということです。
裏を返せば、インターフェースの中にある同じ名前のメソッドを全て実装するだけで自動的にインターフェースが実装されているということになります。

インターフェースの使い方

最後に、main関数の中身を紹介します。

func main() {
    var man Human = Human{Height: 300} // 人間用の構造体を作成
    var cheloniaMydas = Turtle{Kind: "アオウミガメ"} // カメ用の構造体を作成
    var eat Eater // インターフェースEatable型の変数を用意
    fmt.Println("<人間が食べる>")
    eat = man // Human型がインターフェースであるEatable型に変換される
    EatAll(eat) // インターフェースを引数に関数を呼ぶ
    fmt.Println("<カメが食べる>")
    eat = cheloniaMydas // Turtle型がインターフェースであるEatable型に変換される
    EatAll(eat)
}

最初の2行は、構造体を定義してるだけです。
3行目は、インターフェースEatable型の変数を定義しています。ここが、ポイントです。あくまでもこの変数eatEatable型です。もう一度いいますが、インターフェースですからね。
そして5行目でHuman型の構造体の実体をインターフェース型の変数に入れています。この時点で、Eatable型に変換されていると思ってください。
そして、6行目では先程説明したEatAll関数にインターフェースであるeatを渡しています。
これを同様にカメの場合でやります。
すると、結果が人間の場合とカメの場合で変わってることがわかるかと思います。

これをStringerで考える

Tour of Goに出てくるものをそのまま持ってきました。
このチュートリアルが言っていることというのは、

type Stringer interface {
    String() string
}
  • そもそもfmt.Stringerというインターフェースは上記で定義されている。
  • fmt.Stringerfmtパッケージの中にある関数の引数としてこのインターフェースを使っている。

fmtパッケージの中にある関数として代表的なものがfmt.Printlnです。この引数にもfmt.Stringerが使えるということです。

それでは、実際に実装してみましょう。
あれ、実装ってどうやりましたっけ?implementsのようなものはありませんし。
そうです。インターフェースにある関数を全て実装したい型に実装してしまえばそれでおしまいです。

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

今回はPersonに実装したいので・・・
レシーバーにPersonを、そして、Stringerの中にある関数と同じ名前の関数を作り、引数の型、返り値の型も合わせました!
これで実装が完了というわけです。

流れはわかったでしょうか?

もし、次のExerciseがわからない方は、この記事を御覧ください。ものすごく納得がいく説明でわかりやすいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む