ソースコードから理解する技術-UnderSourceCode

手を動かす(プログラムを組む)ことで技術を理解するブログ

RxGoを触ってみた

RxGoというReactiveをGolangで実現するためのライブラリを触ってみました。
READMEのサンプルが分かりやすく、それを見るのがいいとは思いますが、自分の理解のために多少改変してみたりもしたのでサンプルとして上げておきます。

GitHub - SrcHndWng/go-rx-sample

helloworld

一番シンプルなサンプルで、READMEの"Simple Usage"を改修したものになります。
READMEのサンプルとの違いは、time.Sleepを使って1秒毎にメッセージを送信するようにした点です。
実案件ではtime.Sleepの他、何らかのイベントを切っ掛けにメッセージを送信することもあるかと思いますが、そのような使用を想定しました。

コメントに書いたように "not error raise. print 'Done!'"の行を実行すると全てを実行して"Done!"の表示まで実行します。
コメントアウトしてある"error sample"以下のif~else文を実行するようにし、 "not error raise.・・・"をコメントアウトするとobserverのErrHandlerにてエラーがhandleされます。

package main

import (
	"fmt"
	"time"

	"github.com/reactivex/rxgo/observable"
	"github.com/reactivex/rxgo/observer"
)

func main() {
	watcher := observer.Observer{
		NextHandler: func(item interface{}) {
			fmt.Printf("handled: %v\n", item)
		},

		ErrHandler: func(err error) {
			fmt.Printf("error: %v\n", err)
		},

		DoneHandler: func() {
			fmt.Println("Done!")
		},
	}

	message := make(chan interface{})
	source := observable.Observable(message)
	sub := source.Subscribe(watcher)

	go func() {
		cnt := 0
		for {
			if cnt > 4 {
				break
			}
			// error sample
			// if cnt == 3 {
			// 	message <- errors.New("some error")
			// } else {
			// 	message <- fmt.Sprintf("Hello, cnt = %d", cnt)
			// }
			message <- fmt.Sprintf("Hello, cnt = %d", cnt) // not error raise. print 'Done!'
			cnt++
			time.Sleep(1 * time.Second)
		}
		close(message)
	}()

	<-sub
}

grouping

READMEの"Simple Usage"の次のサンプルを改修したものになり、handlerを observer.Newでグループ化したものです。
ただ処理概要は先の"helloworld"と同じにしてあるので、比較してみてください。

package main

import (
	"fmt"
	"time"

	"github.com/reactivex/rxgo/handlers"
	"github.com/reactivex/rxgo/observable"
	"github.com/reactivex/rxgo/observer"
)

func main() {
	onNext := handlers.NextFunc(func(item interface{}) {
		fmt.Printf("handled: %v\n", item)
	})

	onError := handlers.ErrFunc(func(err error) {
		fmt.Printf("error: %v\n", err)
	})

	onDone := handlers.DoneFunc(func() {
		fmt.Println("Done!")
	})

	watcher := observer.New(onNext, onError, onDone)

	message := make(chan interface{})
	source := observable.Observable(message)
	sub := source.Subscribe(watcher)

	go func() {
		cnt := 0
		for {
			if cnt > 4 {
				break
			}
			// error sample
			// if cnt == 3 {
			// 	message <- errors.New("some error")
			// } else {
			// 	message <- fmt.Sprintf("Hello, cnt = %d", cnt)
			// }
			message <- fmt.Sprintf("Hello, cnt = %d", cnt) // not error raise. print 'Done!'
			cnt++
			time.Sleep(1 * time.Second)
		}
		close(message)
	}()

	<-sub
}

observable

READMEの"Recap"欄に「In RxGo, it's useful to think of Observable and Connectable as channels with additional ability to Subscribe handlers.」とあります。
これもREADMEのサンプルを改修したものですが

  • DBへの登録などの代わりに標準出力するようにした
  • エラーについても実装した

などを変えています。

実行してみると"f1"と"f2"の出力順序が逆になりますが、このことからも先に引用した"Recap"欄の旨が分かるかと思います。

package main

import (
	"errors"
	"fmt"
	"time"

	"github.com/reactivex/rxgo/handlers"
	"github.com/reactivex/rxgo/observable"
)

func main() {
	f1 := func() interface{} {

		// Simulate a blocking I/O
		time.Sleep(2 * time.Second)
		return 1
	}

	f2 := func() interface{} {

		// Simulate a blocking I/O
		time.Sleep(time.Second)
		return 2
	}

	f3 := func() interface{} {

		// Simulate a blocking I/O
		time.Sleep(3 * time.Second)
		return 3
	}

	f4 := func() interface{} {

		// Simulate a blocking I/O
		time.Sleep(4 * time.Second)
		return errors.New("some error")
	}

	onNext := handlers.NextFunc(func(v interface{}) {
		fmt.Printf("handled, v = %v\n", v)
	})

	wait := observable.Start(f1, f2, f3, f4).Subscribe(onNext)
	sub := <-wait

	if err := sub.Err(); err != nil {
		fmt.Printf("error raise. err = %v\n", err)
	}

	fmt.Println("finish")
}

GolangでWebAssemblyの復習をした

GolangでWebAssemblyの復習するため、いくつかサイトを見て写経してみました。
一年以上ぶり、かつ実行環境が異なったので、改めて気づいたことがあったので、メモとして残しておきます。

実行環境について

先に書いておくと、goenv、Visual Studio Code 固有のポイントに気づいた感じです。

気づいたポイント

ビルドコマンドはGOOS、GOARCHの指定も含めて一行で行う

https://github.com/golang/go/wiki/WebAssembly#getting-started
にあるように、以下のように一行のコマンドでビルドしないと、ブラウザからの実行時にエラーとなりました。
正しいwasm形式にビルドできないようです。

$ GOOS=js GOARCH=wasm go build -o main.wasm

wasm_exec.js、テンプレートのHTMLのコピー

サイトによってwasm_exec.jsのコピーのコマンドや、テンプレートのHTMLは自作するなどの違いがありましたが、
両方とも「GOROOT」環境変数を使ってコピーし、HTMLはファイル名を変更するのが効率がいいと思いました。

https://blog.gopheracademy.com/advent-2018/go-in-the-browser/
からの引用です。

$ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
$ cp "$(go env GOROOT)/misc/wasm/wasm_exec.html" .
$ mv wasm_exec.html index.html

goexecの実行について

ローカルでWebサーバを動かすのに「goexec」を使う例がありました。
https://github.com/shurcooL/goexec

「go get」で取得して「goexec」コマンドをそのまま実行しても、私の環境ではgoexecが見つからないでエラーとなりました。
goenvで複数のバージョンのgoを入れているためかと思われます。
以下のようにパスを含めたコマンドで実行できました。

$GOPATH/bin/goexec 'http.ListenAndServe(":8080", http.FileServer(http.Dir(".")))'

could not import syscall/js

syscall/jsのimportでVisual Studio Code上で「could not import syscall/js」というエラーとなりました。

https://github.com/Microsoft/vscode-go/issues/1874
こちらのissueにあるように、Visual Studio CodeのWorkspaceのsettings.jsonを以下のようにしたら解決しました。

{
    "go.toolsEnvVars": {
        "GOOS": "js",
        "GOARCH": "wasm",
        "GOROOT": "/usr/local/go",
        "GOPATH": "/home/corey/src/goprojects"
    }
}

以上です。

go-linqを触ってみた

GolangC#LINQのようなデータ操作をできるgo-linqを触ってみました。
GitHub - ahmetb/go-linq: .NET LINQ capabilities in Go

使い方は公式のREADMEにそのまま書いてあるので改めてここには書きませんが
便利そうなので備忘録として残しておきます。

GoのSliceに対して、先に書いたようにLINQ likeな操作をすることができ

  • From(slice)
  • Where(predicate)
  • Select(selector)
  • Union(data)
  • GroupBy(selector)

を使った抽出や、Query型を使用した独自Queryを定義しての抽出などができるようです。
DBから一度に多めのデータを取得 → go-linqで細かい条件を指定しての抽出、などは実案件で割と使えそうな気がします。
とりあえず便利そうなので、気になった方は一度、一番上に張ったリンクのREADMEを一読することをお勧めします。

ginとendressを使ってGraceful Restartを実装してみた

GolangのWeb Frameworkであるgin、"Zero downtime restarts"を行うためのendressを使って
タイトルにあるようにGraceful RestartするWebのサンプルを作ってみました。

https://github.com/gin-gonic/gin
https://github.com/fvbock/endless

サンプルソース

ソースは以下のようになります。

main.go

package main

import (
	"fmt"
	"log"
	"os/exec"
	"syscall"
	"time"

	"github.com/fvbock/endless"
	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	router.GET("/hello", func(c *gin.Context) {
		t := time.Now()
		const layout = "2006-01-02 15:04:05"
		const version = "V1"
		message := fmt.Sprintf("Hello! Now is %s. This version is %s.", t.Format(layout), version)
		c.JSON(200, gin.H{"message": message})
	})
	router.GET("/restart", func(c *gin.Context) {
		pid, err := readPid()
		if err != nil {
			log.Fatal(err)
		}
		err = exec.Command("kill", "-SIGHUP", pid).Start()
		if err != nil {
			log.Fatal(err)
		}
		message := "accept restart. pid = " + pid
		c.JSON(200, gin.H{"message": message})
	})

	server := endless.NewServer("localhost:4430", router)
	server.BeforeBegin = func(add string) {
		pid := syscall.Getpid()
		err := writePid(pid)
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Actual pid is %d", pid)
	}
	err := server.ListenAndServe()
	if err != nil {
		log.Fatal(err)
	}
}

pid.go

package main

import (
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
)

func getPidFilePath() (string, error) {
	exe, err := os.Executable()
	if err != nil {
		return "", err
	}
	return filepath.Dir(exe) + "/pid", nil
}

func writePid(pid int) error {
	path, err := getPidFilePath()
	if err != nil {
		log.Fatal(err)
	}
	file, err := os.Create(path)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()
	_, err = file.Write(([]byte)(strconv.Itoa(pid)))
	if err != nil {
		return err
	}
	return nil
}

func readPid() (string, error) {
	path, err := getPidFilePath()
	if err != nil {
		log.Fatal(err)
	}
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return "", err
	}
	return string(data), nil
}

挨拶と現在日時を返す/hello、(セキュリティ的には微妙ですが)Graceful Restartを行う/restartの2つのGetメソッドを持ちます。
やっていることはソースを見てもらえれば分かりやすいかとは思いますが、ざっと書くと

  • ginでrouterを作り、Getメソッドを実装する
  • そのrouterをendressに渡してserverとして起動する

ことを行っています。

起動時にはpid.go内のwritePid()にてプロセスIDをファイルに出力しておき、/restartが呼ばれた時にはreadPid()で読み込んで使用します。

動作確認

ビルドしてバイナリを起動すると以下のように出力されます。

[GIN-debug] GET    /hello                    --> main.main.func1 (3 handlers)
[GIN-debug] GET    /restart                  --> main.main.func2 (3 handlers)
2019/06/03 22:03:51 Actual pid is 97

同時にpidファイルにプロセスIDが出力されるはずです。
別のターミナルを起動し、動作確認のためにいくつかのコマンドを実行します。

$ curl http://localhost:4430/hello
{"message":"Hello! Now is 2019-06-03 22:05:02. This version is V1."}

$ curl http://localhost:4430/restart
{"message":"accept restart. pid = 97"}

$ curl http://localhost:4430/hello
{"message":"Hello! Now is 2019-06-03 22:06:15. This version is V1."}

$ kill -SIGHUP 202 # 最初にバイナリを起動したターミナルにrestart後のプロセスIDが出力される
$ kill -SIGINT 212

「kill -SIGHUP プロセスID」でGraceful Restartができます。ここで指定するプロセスIDは、バイナリの起動時にターミナルに出力された値です。
URLの/restartでもRestartできますが、実際は「kill -SIGHUP」を内部で呼び出しているだけです。

終了は「kill -SIGINT プロセスID」で行います。

Microsoft TypeScript Vue Starter を vue-loader 15系でやってみた

github.com

3カ月くらい前ですが、TypeScriptの雰囲気を知るため、こちらにあるMicrosoftのTypeScript Vue Starterを途中までやってみました。
チュートリアル形式でREADMEが書かれているので、そのまま進めたのですが、途中で躓いたところがあったのでメモ代わりに書いておきます。

なおタイトルにあるようにvue-loader 15系での話なので、今後はどうなるかは分からないです。

やってみた範囲について

TypeScript Vue StarterのREADMEの「Single File Components」まで以下の修正をしつつ進めました。

webpack.config.jsの修正

npm run build をすると以下のようなエラーとなりました。

vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.

どうもvue-loaderの14→15系に上がった時に変更があったようです。
webpack.config.js にVueLoaderPluginを明示的に追加してあげることで上手くいきました。

const { VueLoaderPlugin } = require("vue-loader");

module.exports = {
  (中略)
  mode: "development",
  module: {
    rules: [
      {
        test: /\.vue$/,
        (中略)
      },
      (中略)
    ]
  },
  plugins: [
    new VueLoaderPlugin()
  ],


もう一つ、やはりnpm run build時に以下のエラーにもなりました。

You may need an appropriate loader to handle this file type.

要は適切なloaderを定義しろ、ってことです。
今回はstyleタグを読めなかったので、webpack.config.js に以下を追加しました。

var path = require('path')
var webpack = require('webpack')
const { VueLoaderPlugin } = require("vue-loader");

module.exports = {
  entry: './src/index.ts',
  (中略)
  module: {
    rules: [
      (中略)
      { 
        test: /\.css$/, 
        loader: 'style-loader!css-loader' 
      },
    ]
  },

Visual Studio CodeのRemote DevelopmentでWSL内のGolangを書いてみる

ちょっと前(だいぶか?)Visual Studio Codeでリモートで接続して編集できる「Remote Development」が登場して気になっていました。
Visual Studio Code Remote Development

登場したときは正規のVisual Studio Codeではなく、プレビュー版?のVisual Studio Code Insidersからしか使えなかったのですが
今回は正規のVisual Studio Codeで使ってみました。

以下、簡単ですがその手順を書きたいと思います。

前提条件

Windows上にWSL(Windows Subsystem for Linux)を入れておいてください。
今回はWSL上にGolangの開発環境を用意し、WSL上のGolangのプロジェクトが編集できるかを試してみました。

手順について

1. Visual Studio CodeにRemote Developmentをインストール
コマンドパレットのInstall Extentionsに「Remote Development」と入力すると検索結果として表示されるので、インストールします。

2. Visual Studio CodeをRemoteで起動
既にWSL上にはGolangのプロジェクトが存在するものとします。
ややこしいのですが、$ bashでWSLに切り替え、WSL上ででプロジェクト直下に移動して「$ code .」を実行します。

$ code .
Installing VS Code Server xxxxxxxxxxxxxxxx
Downloading: 100%
Unpacking: 100%

実行すると上のようになり、Visual Studio Codeが起動します。
Visual Studio Codeのタイトルバーに[WSL]と表示され、[view]-[terminal]でターミナルを軌道するとWSLに切り替わっていれば成功です。

3. Go Extentions を導入
Setup Go Development Environment with VS Code and WSL on Windows
こちらの記事を参考に、Extentisonsとして「Go Extensions」をインストールして設定します。
私の場合は既にインストール済だったので、「Go Extensions」の「Install on WSL」を行い、WSL用のもインストールしました。

4. Go toolsのインストール
Go tools that the Go extension depends on · microsoft/vscode-go Wiki · GitHub
こちらにあるコマンドをコピーして、WSLのターミナルでgo getを実行して必要なものをインストールします。

5. settings.jsonの設定
Setup Go Development Environment with VS Code and WSL on Windows
こちらにあるように、Visual Studio Codeの[file]-[preferences]-[settings]を押下してRemote[WSL]タブを選択します。
[Extentions]-[Go configuration]よりsettings.jsonを開き、先の参考サイトのような定義を追加します。

ただ私の場合は

  • "terminal.integrated.shell.linux"は設定しない
  • "go.formatTool"には"goimports"を指定する
  • "go.gopath"、"go.goroot"は適切なものを指定する

という点を変えました。

"goimports"についてはapt-get等でWSL側にインストールする必要があります。


以上でWSLのGolangVisual Studio Codeで開発できるようになりました。
WSL上のGolangのコードを編集してみましたが、ローカルで開発するときにようにファイル保存時のフォーマット・自動importや構文エラーの表示もできているようです。

Terraformを初めて触ってみた

タイトルにあるようにTerraformに初めて触ってみました。
やったこととしては、

です。

もう何番煎じの記事になるのか分からないですが、自分への備忘録として残しておきます。

インストール

インストールはWindows Subsystem for LinuxUbuntuで行いました。Linuxbrewを入れてあるので、Macと同様のコマンドになるかと思います。

$ brew update
$ brew install terraform

EC2を立てる~削除

既にAWS CLIAWSにアクセスできるよう、~/.aws/credentialsは定義済でした。なのでTerraformの定義ファイルを書き、実行しただけです。
まずTerraformの定義ファイルです。

example.tf

provider "aws" {
  region     = "ap-northeast-1"
}
resource "aws_instance" "example" {
  count         = 1
  ami           = "ami-084040f99a74ce8c3"
  instance_type = "t2.micro"

  tags = {
    Name = "${format("example%02d", count.index + 1)}"
  }
}

ファイル名は何でもいいと思いますが、.tfという拡張子とします。内容としては

  • リージョンにはap-northeast-1、つまり東京を使う
  • exampleという名前で作る
  • 数は一つ
  • AMIはAmazon LinuxのAMI IDを指定
  • インスタンスタイプはt2.micro
  • タグをフォーマットを指定して設定

をしています。

後はexample.tfを保存したフォルダで以下のコマンドを実行します。

$ terraform init      # init 初回だけ?
$ terraform plan    # plan 実行前に構築されるオブジェクトを確認できる
$ terraform apply  # apply 構築するコマンド

「apply」を行うと「Do you want to perform these actions?」と聞かれるので「yes」と入力します。
構築されたら、applyの実行結果とAWSのマネージメントコンソールとで結果を見比べてみましょう。また以下の「show」でも構築した結果を見ることができます。

$ terraform show

最後に構築したものを消して終了します。

$ terraform destroy

以前ちょくちょく使ったServerless Frameworkもそうですが、Terraformのような構成管理ツールはコマンド一発で作った環境を削除できることが、個人的には気に入っています。