Quantcast
Channel: ( ꒪⌓꒪) ゆるよろ日記
Viewing all 61 articles
Browse latest View live

gitでブランチを切り替えた時に何かする(例えばrbenvでRubyのバージョンを切り替えたり)

$
0
0

タイトルの通りのことをやりたかったっぽいので。


例えば、現在のRubyのバージョンはREE 1.8.7だけど、次回リリースからは1.9.3にあげることになっている場合なんか、masterブランチはREE使うけどdevelopブランチは1.9.3で動作させる必要があるっぽいけど、checkoutするたびにrbenv localとかするのダルいしよく忘れるので全力回避したいっぽいです。


で、どうやるかというと、gitのhookでpost-checkoutというのがあり、そこに色々書くとふんわりとやってくれる風味っぽい。


gitリポジトリの.git/hooks/post-checkout をこんな風に書いておくとよいっぽい。

#!/bin/sh# Change ruby versionCURRENT_BRANCH=`git rev-parse --abbrev-ref HEAD`RUBY_VERSION=`git config branch.${CURRENT_BRANCH}.ruby.version`if [$?-ne0];thenRUBY_VERSION=`git config --global ruby.version`fiBUNDLE_GEMFILE=`git config branch.${CURRENT_BRANCH}.ruby.gemfile`if [$?-ne0];thenBUNDLE_GEMFILE=`git config --global ruby.gemfile`fiif [-n"${RUBY_VERSION}"];thenecho"  change local ruby version to ${RUBY_VERSION}"
  rbenv local${RUBY_VERSION}fiif [-n"${BUNDLE_GEMFILE}"];thenecho"  set gemfile to ${BUNDLE_GEMFILE}"export BUNDLE_GEMFILE=${BUNDLE_GEMFILE}fi


使用するRubyのバージョンは、こんな風にglobalな設定と、ブランチ毎の設定をそれぞれやっておく

# globalな設定(systemのrubyを使うっぽい)
git config --gobal ruby.version system

# developの設定
git config --gobal branch.my_cool_branch.ruby.version 1.9.3-p448
git config --gobal branch.my_cool_branch.ruby.gemfile ~/dev/awesome_rails_app/Gemfile.1.9.3

# my_cool_branchの設定
git config --gobal branch.my_cool_branch.ruby.version  2.0.0-p195
git config --gobal branch.my_cool_branch.ruby.gemfile ~/dev/awesome_rails_app/Gemfile.2.0.0


これで、git checkoutした時にpost-checkoutが走って、設定されたバージョンにrbenvで切り替えるっぽい

$ git checkout develop
Switched to branch 'develop'
  change local ruby version to 1.9.3-p448
  set gemfile to ~/dev/awesome_rails_app/Gemfile.1.9.3
$ ruby -v
ruby 1.9.3p448 (2013-06-27 revision 41675) [x86_64-linux]


Rubyのバージョン切替以外に、ブランチ毎になんかしたかったらpost-checkoutに書くとよいっぽい。


git push 進捗

$
0
0
git remote rename origin 進捗
git commit -m '進捗ダメです'
git push 進捗

便利

「関数型Ruby」という病(6) - 関数合成と文脈、Proc#liftとProc#>=、そしてモナ

$
0
0

前回から一年以上が経過しているけど、最近lambda_driver.gemに機能を追加したので、そのことについて書こうと思う。
Rubyで、モナ……っぽい関数合成を実装した話だ。

Rubyで関数合成とかしたいので lambda_driver.gem というのを作った - ( ꒪⌓꒪) ゆるよろ日記

関数合成


関数合成については以前に書いたので、こちらを見て欲しい。

「関数型Ruby」という病(2) - 関数合成 Proc#compose - ( ꒪⌓꒪) ゆるよろ日記


おさらいをしておくと、関数合成とは、 関数gと関数fから、g(f(x))という関数hを新たに作り出すことだ。

(g ∘ f)(x) = g(f(x))


関数gと関数fの合成関数g ∘ fに引数xを渡した結果は、関数gにf(x)の結果を渡したものと等しい。つまり、このような操作である。

f = lambda{|x| x + 1 }
g = lambda{|x| x * x }

# 合成関数g ∘ f
h = lambda{|x| g.(f.(x)) }


これを図にするとこんな感じ。 上記の例の合成関数h: g ∘ fに引数 3を与えた場合。

f:id:yuroyoro:20140216195815p:plain


lambda_driver.gemでは、この関数合成をProc#>>で行うことができるようになっている。

# Proc#>>で合成
h = f >> g

h.(3) # => 16


Proc#>>の実装は単純で、以下のようになる。

classPrcodef>>(g)
   # 「自分の計算結果を引数の関数gへ渡す」Procオブジェクトを返すlambda{|x| g.call(self.call(x)) }
  endend

関数合成と計算の失敗


このように、とても便利な関数合成だが、以下のような状況だと少し困ることがある。

f = lambda{|arr| arr.first } # f: 引数のArrayの最初の要素を取り出す
g = lambda{|x| x + 1 }       # g: 引数に1を加算
h = lambda{|x| x * 2 }       # h: 引数を2倍# f, g, hを合成
i = f >> g >> i

i.([3,5]) # => 4


関数fは、引数に配列を取って最初の要素を返す。関数gはfの結果に1を加算する。関数hはさらにその結果を2倍する。単純だ。
この3つの関数を合成した物が関数i。図にするとこうなる

f:id:yuroyoro:20140216195814p:plain


では、関数iに空配列[]を渡すとどうなるか?

# []を渡すと
i.([])
# => NoMethodError: undefined method `+' for nil:NilClass


関数fはnilを返し、関数gはnilに1を加算しようと+を呼び出してエラーになっている。
図ではこうなる。

f:id:yuroyoro:20140216195813p:plain


ここで、関数がnilを返した場合はその計算は失敗したと仮定する。よって、関数gと関数hでは、引数がnilであるかチェックするように変更する。

# g, hに引数がnilかチェックを追加した
g = lambda{|x| returnnilif x.nil?; x + 1 } 
h = lambda{|x| returnnilif x.nil?; x * 2 }

i = f >> g >> h

i.([]) # => nil


例外も発生せず、めでたしめでたし……ではない。関数gとiにそれぞれnilをチェックする処理が重複して実装されているのでDRYではない。合成する関数がもっと多くなった場合は面倒だ。
できればこのnilをチェックする処理を共通化したい。

関数合成に細工する


関数を合成するときに、「nilかどうか判定する処理」を間に挟むようにすれば、個々の関数にわざわざnilチェックを実装せずともよい。
以下のように関数合成時に細工を行うProc#>=を実装する。

classProcdef>=(g)
    lambda{|x|
      res = self.call(x)
      # 計算結果がnilならば、後続の関数gを呼び出さずにnilを返すreturnnilif res.nil?
      g.call(res)
    }
  endend


これで、Proc#>=を使って細工された関数合成を行うことで、計算が途中で失敗した場合は以降の計算を打ち切るようにできる。

f = lambda{|arr| arr.first }
g = lambda{|x| x + 1 }
h = lambda{|x| x * 2 }

# Proc#>=で合成する
i = f >= g >= h

i.([3,5]) # => 8
i.([])    # => nil


これは、図にするとこのようなイメージである。

f:id:yuroyoro:20140216195812p:plain


合成する関数がどれだけ増えようと問題がない。

j = lambda{|x| x * 3 }

# 新たに関数jを合成
k = f >= g >= h >= j

k.([3,5]) # => 24

k.([]) # => nil


こんどこそめでたしめでたし。

文脈付き関数合成


Proc#>=によって、関数合成の際に細工をすることで、「途中で計算が失敗したら打ち切る関数合成」を実現できた。
では、nilチェックのような「細工」を任意に指定できるようにしてはどうだろうか?


たとえば、「計算の途中結果をputsで標準出力に吐く」関数合成をしたいとする。
そのために、どのような「細工」をするかを設定するProc#liftメソッドを用意しよう。

classProcdeflift(ctx)
    # 引数の「細工」を行うProcオブジェクトをインスタンス変数に設定しておく@ctx = ctx

    # 自身の>=メソッドを定義する(特異メソッド)defself.>=(g)
      lambda{|x|
        # ctxに、合成する関数gと、自身の計算結果を渡して処理を「細工」する@ctx.call(g, self.call(x))
      }.lift(@ctx) # liftの戻り値のProcオブジェクトも同様にliftしておくendselfendend


少々トリッキーな実装なので解説すると、Proc#liftメソッドは細工を行うProcオブジェクト(ctx)を受け取る。
このProcオブジェクト(ctx)は、第一引数にProc#>=メソッドで渡された合成先の関数g、第二引数に合成元の関数fの計算結果であるxを受け取るようにしておく。


liftメソッド内では、特異メソッドとしてProc#>=メソッドを定義する。Proc#>=はインスタンス変数として記憶してあるctxに、合成する関数gと、自身の計算結果を渡して処理を「細工」するようなlambdaを返す。
なお、続けて>=で合成をチェーンできるように、戻り値として返すlambdaも同様に`lift`しておく。


これで準備はできた。
「計算の途中結果をputsで標準出力に吐く」細工を行うctxは、以下のように書く。

ctx = lambda{|g,x|
  # 引数の関数gを呼び出す
  res = g.call(x)  

  # 結果を出力する
  puts "g(#{x}) -> #{res}"

  res
}


では、実際に上記のctxをProc#liftメソッドに渡して、できあがった合成関数を呼び出してみよう。

# Proc#liftで関数合成に細工する
i = f.lift(ctx) >= g >= h

i.call([3,5])
# g(3) -> 4 # ctxから出力# g(4) -> 8 # ctxから出力# => 8


関数gと関数hの呼び出しの後に、標準出力へ結果が出力されていることがわかる。
これは、図にするとこのような感じだ。

f:id:yuroyoro:20140216195811p:plain


先ほどの「nilならば計算を途中で打ち切る」細工は、ctxを以下のように定義すればいい。

ctx = lambda{|g, x|
  # nilならばgを呼び出さずにnilを返して後続の計算を打ち切るreturn x if x.nil? 
  g.call(x)
}


これで、先ほどと同じように動作する。

i = f.lift(ctx) >= g >= h

i.call([4,8]) # => 10

i.call([]) # => nil


この細工を行う関数合成は、前回、前々回の内容を一般化したものだ。

「関数型Ruby」という病(4) - Applicativeスタイル(的ななにか) - ( ꒪⌓꒪) ゆるよろ日記
「関数型Ruby」という病(5) - Object#tryはMaybeモナドの夢を見るか? - ( ꒪⌓꒪) ゆるよろ日記

モナ……


さて、Proc#liftで「細工」を指定することで、様々な細工を関数合成に施すことができるようになった。
ここで、もう一度図を見直してみよう。

f:id:yuroyoro:20140216195810p:plain


先ほどの「nilならば計算を途中で打ち切る」細工は、「失敗するかもしれない計算」という【文脈】上で関数合成が動作しているように見える

f:id:yuroyoro:20140216195809p:plain


「標準出力に吐く」細工は、「結果を出力しながらする計算」という【文脈】上で関数合成が動作しているように見える。 あるいは、関数合成の下に「細工」が【配管】されているように見える。


「細工」を設定するメソッドを`lift`と名付けたのは、実は関数合成を【文脈】上に「持ち上げる」という意味を込めているからだ。


ここで上げたほかにも様々な文脈が考えられる。「外部から環境を与えられる文脈」「複数の結果を組み合わせる文脈」「非同期で計算する文脈」など……。


あれ、それってモナ……。おっと誰か来たようだ。


実際、モナ……則どころかモナ……の形すらしていない(returnもbindもない)のでモナ……ではないのだが、よくわからないと評判のアレも実はこういう配管をやるためのデザインパターンの一種である、と捉えると必要以上に恐怖を覚えずとも済む。

f:id:yuroyoro:20140216221452j:plain

Proc#ymsr


なお、このProc#liftは拙作lambda_driver.gemに実装されており、liftは別名`ymsr`にaliasされている。

Aliased Proc#lift to Proc#ymsr · 953d5d9 · yuroyoro/lambda_driver · GitHub

f:id:yuroyoro:20140216221647j:plain

ʕ  ゚皿゚ ʔ Golangからv8を使う

$
0
0

cgoとlibv8を使って、タイトルのとおりのものを作ってみた。頑張ればnodeみたいなのをgoで書けるかもナー。
ʕ  ゚皿゚ ʔ cgo楽しいおシーゴォー


基本的な方法は以下の記事にあるとおりだが、v8のバージョンが上がっていたりするので、多少の手直しをしてある。

Embedding V8 Javascript Engine and Go | Brave New Method


コードはすべてGithubにある

yuroyoro/golang_v8_Embedding_sample · GitHub


まず、libv8を使う以下のようなwrapperをc++で用意して、cgoから使えるようにしておく。

v8warpper.h

#ifndef _V8WRAPPER_H#define _V8WRAPPER_H#ifdef __cplusplusextern"C" {
#endif// compiles and executes javascript and returns the script return value as stringchar * runv8(constchar *jssrc);

#ifdef __cplusplus
}
#endif#endif// _V8WRAPPER_H


v8warpper.cc

#include <v8.h>#include <string.h>#include "v8wrapper.h"usingnamespace v8;

char * runv8(constchar *jssrc)
{
    // Get the default Isolate created at startup.
    Isolate* isolate = Isolate::GetCurrent();

    // Create a stack-allocated handle scope.
    HandleScope handle_scope(isolate);

    // Create a new context.
    Handle<Context> context = Context::New(isolate);

    // Enter the context for compiling and running the hello world script.
    Context::Scope context_scope(context);

    // Create a string containing the JavaScript source code.
    Handle<String> source = String::New(jssrc);

    // Compile the source code.
    Handle<Script> script = Script::Compile(source);

    // Run the script to get the result.
    Handle<Value> result = script->Run();

    // The JSON.stringify function object
    Handle<Object> global = context->Global();
    Handle<Object> JSON = global->Get(String::New("JSON"))->ToObject();
    Handle<Function> JSON_stringify = Handle<Function>::Cast(JSON->Get(String::New("stringify")));

    Handle<Value> args[] = { result };
    // stringify result
    Local<Value> json = JSON_stringify->Call(JSON, 1, args);

    // Convert the result to an UTF8 string and print it.
    String::Utf8Value utf8(json);

    // return result as string, must be deallocated in cgo wrapperreturn strdup(*utf8);
}


runv8(char)は、引数の文字列のjavascriptをv8で実行し、結果の値をJSON.stringifyした結果の文字列を返す。
なお、サンプルなのでエラー処理はしていない( ;゚皿゚)。

で、こいつを適当にmakeする。以下のmakefileosx

Makefile.warpper

V8_INC=/usr/local/Cellar/v8/3.19.18.4/include
V8_LIBDIR=/usr/local/Cellar/v8/3.19.18.4/lib/libv8.dylib

CC=g++
CFLAGS= -I$(V8_INC) -I/usr/include -lv8 -dynamiclib -o $(TARGET)
SOURCES=v8wrapper.cc
OBJECTS=$(SOURCES:.cc=.o) $(V8_DYLIB)
TARGET=libv8wrapper.dylib

all: $(TARGET)


$(TARGET): $(OBJECTS)
	$(CC) $(CFLAGS) $< -o $@

clean:
	rm $(TARGET) $(OBJECTS)


あとは、cgoでこのv8warpperをlinkして使えばいい。


v8runner.go

package main

// #cgo LDFLAGS: -L. -lv8wrapper -lv8  -lstdc++// #include <stdlib.h>// #include "v8wrapper.h"import"C"import (
	"encoding/json""fmt""unsafe"
)

func RunV8(script string, result interface{}) error {

	// convert Go string to nul terminated C-string
	cstr := C.CString(script)
	defer C.free(unsafe.Pointer(cstr))

	// run script and convert returned C-string to Go string
	rcstr := C.runv8(cstr)
	defer C.free(unsafe.Pointer(rcstr))

	jsonstr := C.GoString(rcstr)

	fmt.Printf("Runv8 json -> %s\n", jsonstr)
	// unmarshal result
	err := json.Unmarshal([]byte(jsonstr), result)
	if err != nil {
		return err
	}

	fmt.Printf("Runv8 Result -> %T: %#+v\n", result, result)
	returnnil
}


RunV8では、'encoding/json'を利用して、runv8の戻り値である文字列のJSONを、goの値にunmarshalする。
以下のように文字列でscriptをRunV8関数に渡せばinterface型で結果を取得できる。

package main

import (
	"fmt"
)
func main() {

	scripts := []string{
		"null",
		"true",
		"123",
		"457.78",
		"[10, 20, 30]",
		"'Hello, World'",
		"new Date()",
		`obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}}`,
	}

	for _, s := range scripts {
		fmt.Printf("Script -> %s\n", s)

		var res interface{}
		RunV8(s, &res)
		fmt.Printf("Result -> %T: %#+v\n\n", res, res)
	}
}

実行結果

Script -> null
Result -> <nil>: <nil>

Script -> true
Result -> bool: true

Script -> 123
Result -> float64: 123

Script -> 457.78
Result -> float64: 457.78

Script -> [10, 20, 30]
Result -> []interface {}: []interface {}{10, 20, 30}

Script -> 'Hello, World'
Result -> string: "Hello, World"

Script -> new Date()
Result -> string: "2014-06-11T08:58:43.951Z"

Script -> obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}}
Result -> map[string]interface {}: map[string]interface {}{"foo":[]interface {}{1, 2}, "bar":map[string]interface {}{"baz":true, "hoge":"fuga"}}


このように、JSONをstructにmappingさせることもできる。

package main

import (
	"fmt"
)

type Foo struct {
	Foo []int`json:"foo"`
	Bar Bar   `json:"bar"`
}

type Bar struct {
	Baz  bool`json:"baz"`
	Hoge string`json:"hoge"`
}

func main() {

	script := `obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}}`var result Foo

	fmt.Printf("Script -> %s\n", script)
	RunV8(string(script), &result)
	fmt.Printf("Result -> %T: %#+v\n", result, result)

}

実行結果

Script -> obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}}
Result -> main.Foo: main.Foo{Foo:[]int{1, 2}, Bar:main.Bar{Baz:true, Hoge:"fuga"}}

ʕ  ゚皿゚ ʔ GolangからLevelDBを使う

$
0
0

cgoとLevelDBを使って、タイトルのとおりのものを作ってみた。頑張ればRiakとかInfluxdbみたいなのを書けるかもナー。
ʕ  ゚皿゚ ʔ cgo楽しいおシーゴォー


コードはすべてGithubにある。

yuroyoro/leveldb-go-sample · GitHub


なお、この実装はあくまで個人的な練習で作ったものなので、まともにLevelDBをGoから使うならばInfluxdbでも使ってるlevigoがおすすめ。
LevelDBはあらかじめinstallしてある想定。 mac osxなのでbrew install leveldbで入った。

cgoでLevelDBをwrapする

まずは、cgoを使ったLevelDBの簡単なwrapperを用意する。単にLevelDBを使うだけなら、感覚的にはsqlite3みたいに、leveldb_openでopenして得られるleveldb_t構造体を使ってputやgetを呼び出し、終わったらcloseすればいい。

leveldb.goから抜粋。

package main

// #cgo LDFLAGS: -lleveldb// #include <stdlib.h>// #include "leveldb/c.h"import"C"import (
	"errors""unsafe"
)

// C Level pointer holdertype LevelDB struct {
	CLevelDB *C.leveldb_t
	Name     string
}

// Open LevelDB with given namefunc OpenLevelDB(path string) (leveldb *LevelDB, err error) {

	cpath := C.CString(path) // convert path to c stringdefer C.leveldb_free(unsafe.Pointer(cpath))

	// allocate LevelDB Option struct to open
	opt := C.leveldb_options_create()
	defer C.leveldb_free(unsafe.Pointer(opt))

	// set open option
	C.leveldb_options_set_create_if_missing(opt, C.uchar(1))

	// open leveldbvar cerr *C.char
	cleveldb := C.leveldb_open(opt, cpath, &cerr)

	if cerr != nil {
		defer C.leveldb_free(unsafe.Pointer(cerr))
		returnnil, errors.New(C.GoString(cerr))
	}

	return&LevelDB{cleveldb, path}, nil
}


上記のOpenLevelDBで、leveldb_openでdatabaseを開く。optionは色々指定できるのだが、サンプルなのでcreate_if_missingだけ指定している。

// Put key, value to databasefunc (db *LevelDB) Put(key, value string) (err error) {

	opt := C.leveldb_writeoptions_create() // write optiondefer C.leveldb_free(unsafe.Pointer(opt))

	k := C.CString(key) // copydefer C.leveldb_free(unsafe.Pointer(k))

	v := C.CString(value)
	defer C.leveldb_free(unsafe.Pointer(v))

	var cerr *C.char
	C.leveldb_put(db.CLevelDB, opt, k, C.size_t(len(key)), v, C.size_t(len(value)), &cerr)

	if cerr != nil {
		defer C.leveldb_free(unsafe.Pointer(cerr))
		return errors.New(C.GoString(cerr))
	}

	return
}

func (db *LevelDB) Get(key string) (value string, err error) {

	opt := C.leveldb_readoptions_create() // write optiondefer C.leveldb_free(unsafe.Pointer(opt))

	k := C.CString(key) // copydefer C.leveldb_free(unsafe.Pointer(k))

	var vallen C.size_t
	var cerr *C.char
	cvalue := C.leveldb_get(db.CLevelDB, opt, k, C.size_t(len(key)), &vallen, &cerr)

	if cerr != nil {
		defer C.leveldb_free(unsafe.Pointer(cerr))
		return"", errors.New(C.GoString(cerr))
	}

	if cvalue == nil {
		return"", nil
	}

	defer C.leveldb_free(unsafe.Pointer(cvalue))
	return C.GoString(cvalue), nil
}


put/getはこんな感じ。C.CStringでkeyやvalueをCの*charに変換しているが、これはmemcpyが走る(ハズ?)なので効率的ではない。levigoの実装では、[]byteに変換してunsafe.Pointerでgoのbyte列のpointerをC側に渡す実装になっているようだ。

net/httpでRESTっぽいガワをつける

goを勉強した事がある人なら誰しもがnet/httpを使った簡単なkvsを書いたことがあるはず。今回はバックエンドをLevelDBにして、net/httpでREST的なガワをつけてみる。

まずはBackend側の実装。 backend.goから。

package main

import (
	"log"
)

type Backend struct {
	db     *LevelDB
	putch  chan putRequest
	delch  chan delRequest
	quitch chanbool
}

type putRequest struct {
	key string
	val string
}

type delRequest struct {
	key string
}

func NewBackend(dbname string) (backend *Backend, err error) {
	db, err := OpenLevelDB(dbname)

	if err != nil {
		return
	}

	log.Printf("LevelDB opened : name -> %s", dbname)

	backend = &Backend{
		db,
		make(chan putRequest),
		make(chan delRequest),
		make(chanbool),
	}

	return
}

func (backend *Backend) Start() {
	gofunc() {
		for {
			select {
			case putreq := <-backend.putch:
				backend.db.Put(putreq.key, putreq.val)
				log.Printf("Backend.Put(%s, %v)\n", putreq.key, putreq.val)
			case delreq := <-backend.delch:
				backend.db.Delete(delreq.key)
				log.Printf("Backend.Delete(%s)\n", delreq.key)
			case<-backend.quitch:
				close(backend.putch)
				close(backend.delch)
				close(backend.quitch)

				log.Printf("Backend stoped")
				return
			}
		}
	}()

	log.Printf("Backend started")
}

func (backend *Backend) Shutdown() {
	backend.quitch <- true
}

func (backend *Backend) Get(key string) (val string, err error) {
	return backend.db.Get(key)
}

func (backend *Backend) Put(key, val string) {
	backend.putch <- putRequest{key, val}

}

func (backend *Backend) Delete(key string) {
	backend.delch <- delRequest{key}
}


NewBackendで先ほど用意したwrapperを使ってLevelDBをopenして、goroutine経由でput/deleteするように実装している。本来ならば、goroutineをsyncして同期っぽいAPIにするべきなのだろうが、面倒なのでサボっている。


次に、net/httpを使ってhttpを処理するserver側。 server.goから。

package main

import (
	"flag""io/ioutil""log""net/http"
)

func main() {

	// parse command line arguments
	dbname := flag.String("name", "./testdb", "Open the database with the specified name")
	addr := flag.String("addr", ":5050", "listen address")

	flag.Parse()

	// open database and start backend
	backend, err := NewBackend(*dbname)
	if err != nil {
		log.Fatal("can't open database", err)
	}

	backend.Start()
	defer backend.Shutdown()

	// listen and serve http
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

		switch r.Method {
		case"GET":
			HandleGet(w, r, backend)
		case"POST", "PUT":
			HandlePut(w, r, backend)
		case"DELETE":
			HandleDelete(w, r, backend)
		}
	})

	log.Printf("Server listening on : %s", *addr)
	http.ListenAndServe(*addr, nil)
}

func HandleGet(w http.ResponseWriter, r *http.Request, backend *Backend) {
	key := r.URL.Path[len("/"):]

	val, err := backend.Get(key)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	if val == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte(val))
}

func HandlePut(w http.ResponseWriter, r *http.Request, backend *Backend) {
	key := r.URL.Path[len("/"):]

	defer r.Body.Close()
	val, err := ioutil.ReadAll(r.Body)

	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	backend.Put(key, string(val))

	w.WriteHeader(http.StatusCreated)
}

func HandleDelete(w http.ResponseWriter, r *http.Request, backend *Backend) {
	key := r.URL.Path[len("/"):]

	backend.Delete(key)

	w.WriteHeader(http.StatusNoContent)
}

起動時の引数でdbのpathとportを貰ってlisten。httpでGET/POST/PUT/DELETEをそれぞれ処理している。見たとおりの実装で、特に難しいことはしていない。

Dockerfile

Dockerfileもあるので、手元でdockerが動けば動作させてみることができる。Dockerhubでimageを配布しようとしたのだが、なぜかpendingのままでimageをbuildしてくれない( ;゚皿゚)。

Dockerfile

ʕ  ゚皿゚ ʔ GolangのWeb Application Frameworkを色々試してみてもいいかしら?

$
0
0

うちのメロンちゃんはLv.117です。

Golangで、簡単なWebアプリケーションをいくつかのフレームワークを用いて作成してみた。
サンプルアプリケーションは、こんな感じのPhotoギャラリーアプリケーションで、画像URLを入力すると追加される。
PureというCSSフレームワークのサンプルから拝借した。

f:id:yuroyoro:20140615172809p:plain


ソースコードGithubで公開している。

yuroyoro/golang_webapp_framework_samples · GitHub


今回試したのは、net/httpパッケージMartiniRevelの3つ。

net/http編

まずは基本のnet/http編。ソースコードこちら


net/httpパッケージでサーバーを書くのはとても簡単だ。以下のように、http.HandleFuncにpathのパターンと処理する関数を登録して、http.ListenAndServeで待ち受けるportを指定すればいい。

func main() {
	http.Handle("/css/layouts/", http.StripPrefix("/css/layouts/", http.FileServer(http.Dir("views/css/layouts"))))
	http.HandleFunc("/", indexHandler)
	http.HandleFunc("/save", saveHandler)
	http.ListenAndServe(":5050", nil)
}


http.HandleFuncに登録する関数は、このように http.ResponseWriterとhttp.Requestを受けるようにしておく。200 OK hello worldを返す関数はこんなん。

func indexHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)  // headerへ200 OKを設定
	io.WriteString(w, "hello, world!\n") // response bodyへwrite

html/template

Golangには、builtinのテンプレートエンジンとしてhtml/templateが提供されている。今回のサンプルアプリケーションでは、これを用いてレスポンスを生成している。以下は、"/"を処理するindexHandler関数。

type Body struct {
	First  models.Photo
	Photos []models.Photo
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	page, err := strconv.Atoi(r.URL.Path[1:])
	if err != nil {
		page = 0
	}

  // databaseから登録されているphotoを読み出す
	body, err := loadBody(page)
	if err != nil {
		panic(err)
	}

  // html/templateでレスポンスを生成して http.ResponseWriterで書き出す
	indexTemplate.Execute(w, body)
}

func init() {
	indexTemplate = template.Must(template.ParseFiles("views/index.html"))
}


indexTemplateは、あらかじめinit関数で"views/index.html"というファイルを読み込んで生成してある。以下は、今回のサンプルで用いているものからの抜粋。

{{range $index, $photo := .Photos }}
    {{with $photo}}
        {{if eq $index 4 5 }}
          <divclass="photo-box pure-u-1 pure-u-med-1-2 pure-u-lrg-2-3"><ahref="{{.URL}}"><imgsrc="{{.URL}}"></a><asideclass="photo-box-caption"><span>by <ahref="https://twitter.com/{{.Author}}">@{{.Author}}</a></span></aside></div>
        {{else}}
          <divclass="photo-box pure-u-1 pure-u-med-1-2 pure-u-lrg-1-3"><ahref="{{.URL}}"><imgsrc="{{.URL}}"></a><asideclass="photo-box-caption"><span>by <ahref="https://twitter.com/{{.Author}}">@{{.Author}}</a></span></aside></div>
        {{end}}
    {{end}}
{{end}}


テンプレート内部では、{{ ... }} で値の展開や分岐や繰り返しを指定する。` {{range $index, $photo := .Photos }}` は、'indexTemplate.Execute(w, body)'で渡したBody構造体のメンバーであるPhotosを元に繰り返しを行う、という意味。

O/R全裸

Golangには、Databaseを扱うための標準的なAPIとしてsql - The Go Programming Languageが提供されている。今回のサンプルではsqltite3を使うので、go-sqlite3をdriverとして用いる。

O/R全裸としては、gorpがよいという話なので使ってみた。go-sqlite3, gorpともにgo getで入れておくこと。

go get github.com/mattn/go-sqlite3
go get github.com/coopernurse/gorp


gorpの使い方については、「Big Sky :: Go言語向けの ORM、gorp がなかなか良い」を見て貰うのが手っ取り早い。
今回のサンプルでは、"models/photos.go"にDatabaseへのアクセスを行う処理を切り出してある。

package models

import (
	"database/sql""errors""github.com/coopernurse/gorp"
	_ "github.com/mattn/go-sqlite3"
)

var DatabaseFile = "photos.db"type Photo struct {
	Id     int64
	URL    string
	Author string
}

func InitDb() (*gorp.DbMap, error) {
	db, err := sql.Open("sqlite3", DatabaseFile)
	if err != nil {
		returnnil, err
	}

	dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
	dbmap.AddTableWithName(Photo{}, "photos").SetKeys(true, "Id")

	err = dbmap.CreateTablesIfNotExists()
	if err != nil {
		returnnil, err
	}

	return dbmap, nil
}

InitDb関数で、databaseをopenしている。Databaseのテーブルにmappingする構造体としてphotoを用意し、gop.DbMap構造体にAddTableWithName関数を用いてmappingを登録している。カラム名と構造体メンバのmappingは基本CoCだが、メンバにtagを指定することでカスタマイズできる。

func (p Photo) Save() error {
	dbmap, err := InitDb()
	if err != nil {
		return err
	}

	defer dbmap.Db.Close()


	// Insert
	dbmap.Insert(&p)
	if err != nil {
		return err
	}

	returnnil
}

func LoadPhotos(page int) ([]Photo, error) {
	dbmap, err := InitDb()
	if err != nil {
		returnnil, err
	}

	defer dbmap.Db.Close()

	if page < 0 {
		returnnil, errors.New("invalid page number")
	}

	limit := 8
	offset := page * limit

	var photos []Photo

	_, err = dbmap.Select(&photos, "SELECT id, url, author FROM photos ORDER BY id DESC LIMIT ? OFFSET ?", limit, offset)
	if err != nil {
		returnnil, err
	}

	return photos, nil
}

Databaseへのinsertはdbmap.Insertに構造体を渡すだけ。dbmap.SelectでSQLを渡すと、結果をmappingした構造体のスライスで取得できる。便利。

testing (builtin)

これで、サーバーとテンプレートとモデルがそろったので、アプリケーションとして動作する形になった。が、すこし寄り道して、テストについても書いておこうと思う。

Golang組み込みのテスティングフレームワークとして、testingパッケージが提供されている。これは本当に単なるテスティングフレームワークの元となるライブラリで、素のままで使うのはちと辛みがある。が、何事も基本から。

"models/photos.go"に対してのテストを"models/photos_test.go"に書く。

package models

import (
	"fmt""testing"
)

func TestSave(t *testing.T) {
	DatabaseFile = "photos_test.db"
	dbmap, err := InitDb()

	if err != nil {
		t.FailNow()
	}

	defer dbmap.Db.Close()

	err = dbmap.TruncateTables()

	if err != nil {
		t.FailNow()
	}

	photo := Photo{
		URL:    "http://example.com",
		Author: "yuroyoro",
	}

	photo.Save()

	var photos []Photo

	_, err = dbmap.Select(&photos, "SELECT id, url, author FROM photos ORDER BY id ASC ")
	if err != nil {
		t.FailNow()
	}

	iflen(photos) != 1 {
		t.Error("Photo.Save() failed")
	}

	if photos[0].URL != photo.URL || photos[0].Author != photo.Author {
		t.Error("Photo.Save() failed")
	}

}

go testコマンドで、"*_test.go"というファイル内でTestから始まる関数が実行される。assert的なのは用意されてなくて気合いのif文とt.Error関数でテストを書いていく。筋力が必要。

実行するとこんな感じになる。

=== RUN TestSave
--- PASS: TestSave (0.01 seconds)
=== RUN TestLoadPhotos
--- PASS: TestLoadPhotos (0.02 seconds)
=== RUN TestModels
Running Suite: Models Suite
===========================
Random Seed: 1402814295
Will run 5 of 5 specs

    •
Ran 5 of 5 Specs in 0.061 seconds
SUCCESS! -- 5 Passed | 0 Failed | 0 Pending | 0 Skipped --- PASS: TestModels (0.06 seconds)
PASS
ok      github.com/yuroyoro/go_shugyo/nethttp/models    0.143s

testing (GoConvey)

GoConveyというテスティングフレームワークを試してみる。BDDっぽい雰囲気。
`go get -t github.com/smartystreets/goconvey`で入れる。

package models

import (
	"fmt"
	. "github.com/smartystreets/goconvey/convey""testing"
)

func TestConverySave(t *testing.T) {
	Convey("Insert", t, func() {
		DatabaseFile = "photos_test.db"
		dbmap, err := InitDb()

		if err != nil {
			t.FailNow()
		}

		defer dbmap.Db.Close()

		err = dbmap.TruncateTables()

		if err != nil {
			t.FailNow()
		}

		photo := Photo{
			URL:    "http://example.com",
			Author: "yuroyoro",
		}

		photo.Save()

		var photos []Photo

		_, err = dbmap.Select(&photos, "SELECT id, url, author FROM photos ORDER BY id ASC ")
		if err != nil {
			t.FailNow()
		}

		So(len(photos), ShouldEqual, 1)
		So(photos[0].URL, ShouldEqual, photo.URL)
		So(photos[0].Author, ShouldEqual, photo.Author)
	})
}

`So(len(photos), ShouldEqual, 1)`とかは、rspecとかに慣れてる人にはとっつきやすいのではないか。

GoConveyにはWebUIが付属しており、goconveyコマンドで起動するとlocalhost:8080でこんな画面が出てくる。ファイルの変更を検知して、自動的にテストを実行してくれる簡易CIのようだ。

f:id:yuroyoro:20140615172846p:plain

testing (Ginkgo)

今度は、GinkgoというBDDテスティングフレームワークを試す。assertionライブラリとしてgomegaというパッケージに切りだされている。

go get github.com/onsi/ginkgo/ginkgo
go get github.com/onsi/gomega


ginkgo bootstrapでtestのひな形が作成される。 テストコードはこんな感じになる。rspec風だが、func()の連打が辛み。

package models_test

import (
	"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega""github.com/coopernurse/gorp""github.com/yuroyoro/go_shugyo/nethttp/models""testing"
)

func TestModels(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Models Suite")
}

var _ = Describe("Photo", func() {
	var (
		dbmap *gorp.DbMap
		err   error
	)

	BeforeEach(func() {
		models.DatabaseFile = "photos_test.db"
		dbmap, err = models.InitDb()
		if err != nil {
			panic(err)
		}

		err = dbmap.TruncateTables()
		if err != nil {
			panic(err)
		}
	})

	AfterEach(func() {
		dbmap.Db.Close()
	})

	Describe("Insert", func() {
		It("should inserts new record", func() {
			photo := models.Photo{
				URL:    "http://example.com",
				Author: "yuroyoro",
			}

			photo.Save()

			var photos []models.Photo

			_, err = dbmap.Select(&photos, "SELECT id, url, author FROM photos ORDER BY id ASC ")
			if err != nil {
				Fail("Failed to load records from database")
			}

			Expect(len(photos)).To(Equal(1))
			Expect(photos[0].URL).To(Equal(photo.URL))
			Expect(photos[0].Author).To(Equal(photo.Author))

		})
	})
})

ginkgo -vでテスト実行。

ozaki@mbp-2 ( ꒪⌓꒪) $ ginkgo -v
............Running Suite: Models Suite
===========================
Random Seed: 1402820295
Will run 5 of 5 specs

Photo Insert
  should inserts new record
  /Users/ozaki/dev/go/src/github.com/yuroyoro/golang_webapp_framework_samples/nethttp/models/models_suite_test.go:61
•
------------------------------
Photo LoadPhotos when given page 0
  should returns first page
  /Users/ozaki/dev/go/src/github.com/yuroyoro/golang_webapp_framework_samples/nethttp/models/models_suite_test.go:87
•
------------------------------
Photo LoadPhotos when given page 2
  should returns last page
  /Users/ozaki/dev/go/src/github.com/yuroyoro/golang_webapp_framework_samples/nethttp/models/models_suite_test.go:101
•
------------------------------
Photo LoadPhotos when given page 99
  should returns empty
  /Users/ozaki/dev/go/src/github.com/yuroyoro/golang_webapp_framework_samples/nethttp/models/models_suite_test.go:110
•
------------------------------
Photo LoadPhotos when given page -1
  should returns error
  /Users/ozaki/dev/go/src/github.com/yuroyoro/golang_webapp_framework_samples/nethttp/models/models_suite_test.go:119
•
Ran 5 of 5 Specs in 0.054 seconds
SUCCESS! -- 5 Passed | 0 Failed | 0 Pending | 0 Skipped PASS

Ginkgo ran in 1.521415799s
Test Suite Passed


スティングについてはここまで。ほかにも色々あるので、「go言語のテスティングフレームワークについて — さにあらず」などを参考にサレタシ。

Martini編

さて、前置きが長くなったが、GolangでのWeb Application FrameworkであるMartiniを試してみる。
日本語のドキュメントもある。

net/httpで書いたサンプルを移植する。ソースコードこちら

package main

import (
	"./models""fmt""github.com/codegangsta/martini""github.com/codegangsta/martini-contrib/render""net/http""strconv"
)

func main() {
	m := martini.Classic()

	m.Use(render.Renderer())
	m.Use(martini.Static("views"))

	m.Get("/", func(w http.ResponseWriter, r *http.Request, render render.Render) {
		page, err := strconv.Atoi(r.URL.Path[1:])
		if err != nil {
			page = 0
		}

		body, err := loadBody(page)
		if err != nil {
			panic(err)
		}

		render.HTML(200, "index", body)
	})

	m.Post("/", func(w http.ResponseWriter, r *http.Request, render render.Render) {
		url := r.FormValue("url")
		author := r.FormValue("author")

		if url == "" {
			render.Error(500)
			return
		}

		if author == "" {
			render.Error(500)
			return
		}

		fmt.Printf("Save Photo(%s, %s)", url, author)
		photo := models.Photo{
			URL:    url,
			Author: author,
		}
		photo.Save()

		render.Redirect("/", 302)
	})

	m.Run()
}

みてのとおり、Sinatra風だ。m.Getやm.Postに関数を登録している。net/httpと異なる点として、render.Renderを利用してステータスコードや、テンプレートのレンダリングを実行している。


登録する関数は実は任意の引数、戻り値にすることが可能で、Martiniはリフレクションを用いて引数や戻り値の解決をしている。上記のコードで引数で受けているrender.Renderは、Martiniの持つDI機能によって、リフレクションで動的に解決されている。

以下はサンプルより。戻り値としてステータスコードとbodyの中身を返している。

m.Get("/", func() (int, string) {
  return418, "i'm a teapot"// HTTP 418 : "i'm a teapot"
})

他にも、引数に構造体を受け取る関数を登録すると、リクエストパラメータを構造体にmappingしてくれたりするらしい。このあたりのリフレクションを用いた黒魔術が「Go的ではない」という理由で批判されていたりもする。

Revel編

次は、Revelを試してみる。MartiniがSinatraだとすると、RevelはRailsに相当する感じ。コードはこちら


revelコマンドで色々と出来るようになっている。

ozaki@mbp-2(꒪⌓꒪) $ revel
~
~ revel! http://revel.github.io
~
usage: revel command [arguments]

The commands are:

    new         create a skeleton Revel application
    run         run a Revel application
    build       build a Revel application (e.g. for deployment)
    package     package a Revel application (e.g. for deployment)
    clean       clean a Revel application's temp files    test        run all tests from the command-lineUse "revel help [command]" for more information.

アプリケーション生成

revel new myappでアプリケーションのひな形ができる。Rails風。

ozaki@mbp-2(꒪⌓꒪) $ revel new myapp
~
~ revel! http://revel.github.io
~
Your application is ready:
   /Users/ozaki/dev/go/src/myapp

You can run it with:
   revel run myapp


ディレクトリ構成はこうなっている。app以下にcontroller/viewsがある。

ozaki@mbp-2(꒪⌓꒪) $ tree .
.
├── app
│   ├── controllers
│   │   └── app.go
│   ├── init.go
│   └── views
│       ├── App
│       │   └── Index.html
│       ├── debug.html
│       ├── errors
│       │   ├── 404.html
│       │   └── 500.html
│       ├── flash.html
│       ├── footer.html
│       └── header.html
├── conf
│   ├── app.conf
│   └── routes
├── messages
│   └── sample.en
├── public
│   ├── css
│   │   └── bootstrap.css
│   ├── img
│   │   ├── favicon.png
│   │   ├── glyphicons-halflings-white.png
│   │   └── glyphicons-halflings.png
│   └── js
│       └── jquery-1.9.1.min.js
└── tests
    └── apptest.go


revel run myappでサーバー起動。

ozaki@mbp-2(꒪⌓꒪) $ revel run myapp
~
~ revel! http://revel.github.io
~
INFO  2014/06/1516:34:46 revel.go:320: Loaded module static
INFO  2014/06/1516:34:46 revel.go:320: Loaded module testrunner
INFO  2014/06/1516:34:46 run.go:57: Running myapp (myapp)in dev mode
INFO  2014/06/1516:34:46 harness.go:165: Listening on :9000


f:id:yuroyoro:20140615172906p:plain

昔のPlayっぽいアレだ。ヒィッ...。

ルーティング

さて、実装だが。まずはconf/routesにルーティングの定義を書く。

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

module:testrunner

GET     /                                       App.Index
GET     /photos/*                               Photos.Index
POST    /photos                                 Photos.Save

# Ignore favicon requests
GET     /favicon.ico                            404

# Map static resources from the /app/public folder to the /public path
GET     /css/*filepath                       Static.Serve("public/css")

# Catch all
*       /:controller/:action                    :controller.:action

あれ、なんかPlayっぽい……。'/hotels/:id 'のようにpathからparameterを切り出す機能もある。routesの定義は型安全である必要がある。つまり、controllerに対応するメソッドがないとエラーになる。うーんPlay……。

controller

さて、controller側の実装だが。生成されたapp/controllers/app.goを見てみると、Appという構造体がある。これがアプリケーションで使うcontrollerのベースとなるもので、各controllerはこのAppを構造体埋め込みで埋め込んでおくことで、フレームワークが提供する様々な機能を利用することができるようだ。

package controllers

import"github.com/revel/revel"type App struct {
	*revel.Controller
	GorpController
}

func (c App) Index() revel.Result {
	return c.Render()
}


今回のアプリケーションでは、Gorpを用いたDatabaseへのアクセスやトランザクションを提供するGorpControllerを用意してAppに埋め込むことで、Database関連の機能をcontrollerに追加している。

package controllers

import (
	"database/sql""github.com/coopernurse/gorp"
	_ "github.com/mattn/go-sqlite3"
	r "github.com/revel/revel""github.com/revel/revel/modules/db/app"
	m "github.com/yuroyoro/go_shugyo/revel_sample/app/models"
)

var (
	Dbm *gorp.DbMap
)

func InitDB() {
	db.Init()
	Dbm = &gorp.DbMap{Db: db.Db, Dialect: gorp.SqliteDialect{}}

	Dbm.AddTableWithName(m.Photo{}, "photos").SetKeys(true, "Id")

	Dbm.TraceOn("[gorp]", r.INFO)

	err := Dbm.CreateTablesIfNotExists()
	if err != nil {
		panic(err)
	}

	photos := []*m.Photo{
		&m.Photo{URL: "http://24.media.tumblr.com/d6b9403c704c3e5aa1725c106e8a9430/tumblr_mvyxd9PUpZ1st5lhmo1_1280.jpg", Author: "Dillon McIntosh"},
	}

	for _, photo := range photos {
		if err := Dbm.Insert(photo); err != nil {
			panic(err)
		}
	}

}

type GorpController struct {
	*r.Controller
	Txn *gorp.Transaction
}

func (c *GorpController) Begin() r.Result {
	txn, err := Dbm.Begin()
	if err != nil {
		panic(err)
	}
	c.Txn = txn
	returnnil
}

func (c *GorpController) Commit() r.Result {
	if c.Txn == nil {
		returnnil
	}
	if err := c.Txn.Commit(); err != nil&& err != sql.ErrTxDone {
		panic(err)
	}
	c.Txn = nilreturnnil
}

func (c *GorpController) Rollback() r.Result {
	if c.Txn == nil {
		returnnil
	}
	if err := c.Txn.Rollback(); err != nil&& err != sql.ErrTxDone {
		panic(err)
	}
	c.Txn = nilreturnnil
}


具体的なcontrollerの実装は、こんな感じになった。Photo構造体にAppを埋め込んでいる。

package controllers

import (
	"fmt""github.com/revel/revel""github.com/yuroyoro/go_shugyo/revel_sample/app/models""github.com/yuroyoro/go_shugyo/revel_sample/app/routes"
)

type Photos struct {
	App
}

func (c Photos) Index(page int) revel.Result {

	records, err := models.LoadPhotos(c.Txn, page)

	if err != nil {
		panic(err)
	}

	fmt.Println(records)

	first := records[0]
	photos := records[1:]

	return c.Render(first, photos)
}

func (c Photos) Save(photo models.Photo) revel.Result {

	photo.Validate(c.Validation)

	if c.Validation.HasErrors() {
		c.Validation.Keep()
		c.FlashParams()
		return c.Redirect(routes.Photos.Index(0))
	}

	err := c.Txn.Insert(&photo)
	if err != nil {
		panic(err)
	}

	return c.Redirect(routes.Photos.Index(0))
}


RevelのcontrollerもMartini同様、関数の引数へのData-Bindingの機能を提供している。レスポンスについては、c.Renderでapp/views/template以下から適切なファイルが選択されてレンダリングされるようになっている。テンプレート自体は、html/templateをRevelが拡張したもので、ほぼ同じように使うことができる。

まとめ

駆け足で、GolangでのWeb Application作成を3つのフレームワークを用いて紹介してみた。他にも、Martiniへのカウンターとしてのhttps://github.com/codegangsta/negroni:Negroniや、静的ファイルもバイナリにまとめてることができるKochaなどがある。

個人的には、Revelはちょっと重量級でMartiniくらいがちょうどよいが、今後もSinatraRailsみたいな位置づけでそれぞれ使い分ける感じになるのではなかろうか。

以下、参考URL。

オマエらはもっとObject#tryの便利さについて知るべき

$
0
0
  arr = [
    ["foo", "", "bar"], 
    nil,
  ].sample
  
  arr.try(:reject, &:blank?) #=> [“foo”, “bar”]
  • Object#tryはnil-safeなcallとして使える
  • blockを取るメソッドのsymbolを渡した場合に、第二引数にprocを渡しても動作する

activesupport.gem
tryと関数合成は本体に入れて欲しい

ʕ  ゚皿゚ ʔ GolangのASTを可視化するツールを作った

$
0
0

はじめてのGo Runtime。
ということで、GoのAST(抽象構文木)を可視化するツールを書いた。

yuroyoro/goast-viewer · GitHub

goast.yuroyoro.netにデモがある。

go/astパッケージを使うと、GoのソースコードからAST(抽象構文木)を得ることができる。
あとはこれをAngulerJSとか使ってみて可視化してみただけ。

f:id:yuroyoro:20140630220303p:plain

ソースコードをアップロードするか、入力してparseボタンを押すと、右側にASTが展開される。マウスオーバーするとASTのnodeに該当するコードが選択状態になる。

以下の手順でインストールできるます

$ go get -d github.com/yuroyoro/goast-viewer
$ cd $GOPATH/src/github.com/yuroyoro/goast-viewer
$ make install

GoよりAngulerJSの方が難しかったʕ  ゚皿゚ ʔ


ぼくのかんがえたさいきょうのGit Repository Browser: Gitterb をRuby2.1.2/Rails4にupgradeしてDockerImage作った話

$
0
0

3年ほど前に、GitterbというGitリポジトリのコミットログを可視化するツールを作った。


このアプリケーションはRuby1.9/Rails3.2 で書かれていて、今となってはもう動かないので、Ruby2.1/Rails4へupgradeした。

デモサイトはこちら http://gitterb.yuroyoro.net/


依存しているGritというRubyからGitリポジトリをホゲるGemがRuby2系では動かないので、libgit2のRubyバインディングであるRuggedに移行している。

あと、せっかくなのでCentOS7で動くDockerImageを作った。Docker Hubにおいてある。

以下のようにdocker pullした後にrunすると、port3000でサンプルが起動する。

docker pull yuroyoro/gitterb
docker run -d-p3000:3000-t yuroyoro/gitterb


macでboot2dcoker使ってる人は、port forwardingしてくだされ。

ssh -N-L3000:127.0.0.1:3000 docker@localhost -p2022

( ꒪⌓꒪) あばばばばばばばば

golang勉強会で「cgoやってみた」という話をしてきた

$
0
0

Go lang勉強会 - connpassで発表してきた。
今までにblogに書いたcgoの話。

以下が資料とサンプルコード。

久しぶりに人前で発表した気がする。
当日はdemoもやるとなると時間が足りなくなるだろうという予想通りになり、ブランクを感じた。
あと、ネタ成分が不足気味だったのでリハビリしなければならない。

他の人の発表も面白かった。特にライセンスの話など。皆さんお疲れ様でした。

こんな勉強会なら、また参加したいものです。

"err"という文字列をHighlightしておくとGolangのコードリーディングが捗る

$
0
0

f:id:yuroyoro:20140812144144p:plain

vimの人はこんな感じで

autocmd FileType go :highlight goErr cterm=bold ctermfg=214
autocmd FileType go :match goErr /\<err\>/

表参道.rb #4で「本当は怖い オープンクラスと Duck Typing」というLTをやった話

「commit-m: GitHubコミットメッセージの文例が検索できるサービス」がとても便利だったのでcliから使えるコマンド書いた

$
0
0

http://commit-m.minamijoyo.com/:titeleという有名OSSのコミットメッセージを検索できるサービスがあって、英語のコミットメッセージを書くときに「あれ? これどういう風に書けばいいんダー」ってときに例文を検索できて捗る。

commit-m.minamijoyo.com

が、自分の場合はコミットメッセージ書くときはvimとか git commit -mとかからなのでCLIで検索できたらより捗るかと思ってGolangで書いた。 APIとかは無いようなのでクロールしてる。 GoQuery使えばこの手のクローラーが一瞬でかけるのでよさがある。

github.com

go get github.com/yuroyoro/gommit-mで入れた後に gommit-m keyword [page]で検索できる。

f:id:yuroyoro:20151110131804p:plain

「関数型Ruby」という病(7) - Elixir's Pipe operator |> in Ruby

$
0
0

最近Elixirが人気ですよね。ErlangVM上でOTPの恩恵を受けながら簡潔な記法で並行処理を書ける言語ということで話題になっていますな? Elixirは関数型プログラミングのエッセンスを取り入れていると言われており、そのひとつにPipe演算子(|>) がある。

Pipe演算子(|>)とは何かというと、左辺の値を右辺の関数の第1引数に適用する演算子

iex> [1, [2], 3] |> List.flatten()
  [1,  2,  3]


上記のコードは、左辺の[1, [2], 3]を 右辺の List.fatten(list)の引数として渡す。 このPipe演算子は、Streamモジュールなどと合わせて利用するとデータが左から右へ流れている模様をコードとし視覚化することができるという利点があるっぽい(感じろ)。

iex(16)> f = fn a -> IO.puts "f(#{a}) : #{a+1}"; a ; end
#Function<6.90072148/1 in :erl_eval.expr/5>

iex(17)> g = fn a -> IO.puts "g(#{a}) : #{a*2}"; a * 2 ; end
#Function<6.90072148/1 in :erl_eval.expr/5>

iex(18)> 1..10 |> Stream.map(f) |> Stream.map(g) |> Enum.take(3)
f(1) : 2
g(1) : 2
f(2) : 3
g(2) : 4
f(3) : 4
g(3) : 6
[2, 4, 6]


1..10 |> Stream.map(f) |> Stream.map(g) |> Enum.take(3)というコードで、1から10のStreamに対してlazyに関数fとgを順番に適用しながら3つの要素を取り出すという様を素直に表現できていますね?(思え)

さて、そんな便利なパイプ演算子ですが、実は2年ほど前に作ったlambda_driver.gemに既に実装されていたりする。

Rubyでは中置演算子を独自に定義することはできないので、 Objectクラスに |>というメソッドを生やすことで実現しよう(全角ェ)。 素朴な実装はこうだ

classObjectdef|>(f = nil)
  puts f
    if block_given?
      yieldselfelse 
      f.call(self)
    endend

  alias_method "|>", :>=end


さて、この全角の |>を使って、上記のElixirのコードをRubyで書いてみるとどうなるか?

irb(main):059:0> f = ->(a){ puts "f(#{a}) : #{a + 1}" ; a + 1}
=> #<Proc:0x007ffb8d8b2348@(irb):59 (lambda)>

irb(main):060:0> g = ->(a){ puts "g(#{a}) : #{a *2}" ; a * 2}
=> #<Proc:0x007ffb8d860a98@(irb):60 (lambda)>

irb(main):061:0>  (1..10).|>(&:lazy).|>{|x| x.map(&f) }.|>{|x| x.map(&g) }.|>{|x| x.take(3)}.|>(&:force)
f(1) : 2
g(2) : 4
f(2) : 3
g(3) : 6
f(3) : 4
g(4) : 8
=> [4, 6, 8]


なんというか、すごく……ダサいですね……。

というか、Rubyだったら素直にこう書いた方がいい

irb(main):143:0> (1..10).lazy.map(&f).map(&g).take(3).force
f(1) : 2
g(2) : 4
f(2) : 3
g(3) : 6
f(3) : 4
g(4) : 8
=> [4, 6, 8]


ごくごくまれに、左辺値がObjectとかでrevapplyを使いたくなることもなきにしもあらずだが、そういう場合でも大抵は Object#tryで事足りる。

結論 : Rubyには必要ないのでは?

ディスク使用量をFlameGraphで可視化する

$
0
0

こんにちわ。しいたけです。今日はディスク使用量をFlameGraphにするツールの話です。

FlameGraphについては、 Flame GraphsGolangでFlame Graphを描く | SOTAを読んでもらうのが手っ取り早いのですが、ようはプロファイル結果を可視化する方法です。縦軸が呼び出しの階層に、横軸がサンプル数や実行時間などに対応しており、どの関数が支配的かを直感的に見ることができる優れたグラフですよ。

で、このFlameGraph、別にプロファイル結果だけではなく、ツリー構造で各ノードが量を持つ場合に、枝毎の累積量を可視化するのに利用できます。プロファイル以外に、ツリー構造でノードが量を持つ例として、ディレクトリ階層毎のディスク使用量が考えられます。

というわけで、指定ディレクトリ以下のディスク使用量をFlameGraph化するツールを書きました。

GitHub - yuroyoro/du-flamegraph: visualize disk usage as flamegraph

こんな感じのグラフが出力されます

http://yuroyoro.net/du-flamegraph.svg


goで書かれており、使い方は、 `go get -u yuroyoro/du-flamegraph` でインストールできます。

このツールは、 FlameGraphの描画に `flamegraph.pl` というスクリプトが必要であり、これは GitHub - brendangregg/FlameGraph: Stack trace visualizerにあります。
これを git cloneなどで手元に入れて、 $PATHに追加するか、 `--flamegraph-script` で位置を指定するかしてやれば、FlameGraph がsvgとして出力されます。

NAME:
   du-flamegraph - visualize disk usage as flamegraph

USAGE:
   du-flamegraph [global options] [FILE]

VERSION:
   0.0.0

GLOBAL OPTIONS:
   --width value, -w value    width of image (default 1200) (default: 1200)
   --height value, -h value   height of each frame (default 16) (default: 16)
   --flamegraph-script value  path of flamegraph.pl. if not given, find the script from $PATH
   --out value                distination path of grenerated flamegraph. default is ./du-flamegraph.svg (default: "./du-flamegraph.svg")
   --verbose                  show verbose log
   --version, -v              print the version

FlameGraph、色々と応用がききそうですね。


go tool traceでgoroutineの実行状況を可視化する

$
0
0

こんにちわ。しいたけです。今日はgoroutineの実行状況をいいかんじに可視化するツールの話です。

goのプロファイリングツールと言えば、 runtime/pprofnet/http/pprofですよね。これらの使い方はググればすぐに出てくるのですが、 詳細なtraceを取得して可視化できる runtime/traceについては、日本語の情報が殆ど無いので書いてみましいたけ。

runtime/traceはgoroutineの実行状況やsystem callのイベント、Heapやnetworkの状況をこんな感じに可視化してくれるので便利です。

f:id:yuroyoro:20171211191843p:plain

これは自作のクローラーを動かしている際のtraceを可視化したもので、横軸がタイムラインになっており、上段に Heapの使用状況やgoroutineとos threadの数が, 下段はnetworkやProccesor(GOMAXPROCSで指定するgoroutineの実行環境)毎にどのコードが実行されているか、が表示されます。

Heapやgoroutines数の増減と処理の関連を時系列で追えるので、大まかなボトルネックの特定に便利です。また、各goroutine毎の開始/終了とsystem callやunblockイベント(goroutineの切り替え)を細かく追えるので、goroutineが刺さっている箇所の特定にも役立ちます

右側のboxの↓アイコンをクリックした上で、グラフ上でドラッグするとzoomin/outできます。また、View Options で Flow Eventsをチェックすると矢印が描画されます

ここに、実際に動かせるサンプルを置いておきます。

f:id:yuroyoro:20171211191957p:plain

これはとある自作Proxyのtraceの一部で、Proc3で実行されている皆さんおなじみの net/http.(*conn).serveがリクエストを受けて、 go 構文で新たなgoroutineを起動してProc2で実行される様子です。このように、どのタイミングでどのgoroutineが動いたのかが一目瞭然です。

f:id:yuroyoro:20171211192018p:plain

これはGCが実行されている様子です。ほとんどのProcでGC関連の処理が動いた後に、上段のHeapのallocatedが減っている様子が見てとれます。

で、このtraceの可視化の方法ですが、 手っ取り早いのは runtime/traceパッケージをimportして、 trace.Start(w io.Writer)trace.Stop()を呼び出す方法です

https://golang.org/pkg/runtime/trace/

package main

import (
    "log"
    "os"
    "runtime"
    "runtime/trace"
)

func main() {

    f, err := os.Create("trace.out")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    trace.Start(f)
    defer trace.Stop()

  // do something ...
}

このように、traceを取得したい処理の前後で trace.Startを呼び出すと、結果がファイルに出力されます。 そのファイルを go tool traceコマンドの引数に渡すと、ブラウザで取得したtraceを分析できるようになります。

net/http/pprofパッケージを使っているウェブアプリケーションでも、簡単にtraceを取得することができます。

pprofがlistenしているなら、 /debug/pprof/trace?seconds=5にリクエストを送ると、5秒間のtraceを取得して結果をダウンロードできます。得られたファイルを go tool traceに渡せば、同様にブラウザ上で分析できます。

詳細なボトルネックの分析は、 pprofやgo-torchでのflamegraphの分析が有向ですが、 go tool traceでは時系列に対応しての分析が可能なので、それぞれ状況に合わせて使い分けると良いのではないかと思いましいたけ。

F.Y.I.

作って学ぶ 「Https Man in The Middle Proxy」 in Go

$
0
0

ᕕ( ᐛ )ᕗ こんにちわ、しいたけです。

webのhttps化が推進される昨今ですね?
https通信は経路上での通信内容が盗聴・改竄されるのを防ぐことができますが、開発用途でhttps通信の内容を確認したい場合が稀にあります。
そのような場合は mitmproxyなどを導入すればよいのですが、せっかくなので実際にこのようなProxyをGoで実装してみて、 中間者攻撃(Man-in-The-Middle Attack)がどのような手法でhttps通信を盗聴・改竄するのか確かめてみました。

実際に書いたProxyのコードはこちらです

yuroyoro/mitm_proxy_sample

https proxy と HTTP CONNECT tunneling

まず、通常のhttps Proxyの動作を確認してみましょう。

httpsでは、ProxyはクライアントからのCONNECTメソッドを受信すると、クライアントに代わって対象ホストとのTCPコネクションを確立し、以降はクライアントと対象ホストのTCP通信を転送します。クライアント-ホスト間のTLS接続のhandshakeもproxyを経由して行わます。
この方式により、Proxyを経由しつつクライアント-ホスト間でTLSセッションが確立され、Proxyを経由しつつも経路上では暗号化された通信が可能となります。

図にするとこんな感じです

f:id:yuroyoro:20180216193239p:plain

実装

では具体的な実装を見てましょう。
まずはおなじみ ServeHTTPです。クライアントから CONNECTメソッドが送信されたら、通信を転送する relayHTTPSRequestを呼び出します

https://github.com/yuroyoro/mitm_proxy_sample/blob/master/main.go#L34

func (proxy *MiTMProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  // CONNECT メソッドが来たらif r.Method == http.MethodConnect {
        if proxy.mitm {
            proxy.mitmRequest(w, r)  // Man in The Middleする
        } else {
            proxy.relayHTTPSRequest(w, r) // Tunnelingで通信を転送する
        }
        return
    }

    proxy.transportHTTPRequest(w, r)
}

実際に通信を転送しているコードは以下のとおりです。処理の流れはコメントを読んでもらえばわかると思いますが、やっていることは単純です。
CONNECTメソッドで指定されたホストにTCP接続を張って、クライアントからの通信をそのまま流し込むだけです

https://github.com/yuroyoro/mitm_proxy_sample/blob/master/https.go#L12

func (proxy *MiTMProxy) relayHTTPSRequest(w http.ResponseWriter, r *http.Request) {
    proxy.info("relayHTTPSRequest : %s %s", r.Method, r.URL.String())

    // CONNECT先のHostへTCPコネクションを張る
    dest, err := net.Dial("tcp", r.Host)
    if err != nil {
        http.Error(w, err.Error(), http.StatusServiceUnavailable)
        return
    }

    // http.Hicjacker を利用してクライアントとの生のTCPコネクションを取り出す
    conn := hijackConnect(w)
    // クライアントには200 OKを返す。これでクライアントはこのTCP接続にHTTPリクエストを送ってくる
    conn.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))

    proxy.info("relayHTTPSRequest : start relaying tcp packets %s %s", r.Method, r.URL.String())

    // クライアント-対象Host間のTCP通信をそのまま中継するgo transfer(dest, conn)
    go transfer(conn, dest)
}

func transfer(dest io.WriteCloser, source io.ReadCloser) {
    defer dest.Close()
    defer source.Close()
    io.Copy(dest, source)
}

Goには http.Hijackerという便利なインターフェースがあり、 http.ResponseWriterから生のクライアントとのTCP接続を取り出すことができます。
これを利用して、TCP通信の転送を行っています

func hijackConnect(w http.ResponseWriter) net.Conn {
    hj, ok := w.(http.Hijacker)
    if !ok {
        panic("httpserver does not support hijacking")
    }

    conn, _, err := hj.Hijack()
    if err != nil {
        panic("Cannot hijack connection " + err.Error())
    }

    return conn
}

実際はtimeoutなどを考慮した実装をすべきなのですが、これだけでも動きます。

Man in The Middel Proxyの仕組み

では、本題の中間者攻撃を行うProxyについてです。

通常のhttps proxyでは、クライアントからの CONNECTメソッドを契機に、対象HostとのTCP通信を中継していました。
Proxyを流れる通信内容はTLSによって暗号化されており、内容を盗聴・改竄することはできません。

しかし、対象Hostとクライアント間のTLS handshakeもProxyを経由するので、この段階でクライアントからのTLS handshakeを、対象ホストになりすましてProxyが行うとどうなるでしょうか?
つまり、Proxyは対象ホストのサーバー証明書をその場で生成して署名し、クライアントに提示します。
もちろん、Proxyが署名したサーバー証明書は信頼できないCAのものとしてブラウザには警告が出ますが、そのままユーザーが続行することでTLS handshakeが成功します。
クライアントは確立したTLS接続を対象ホストとのものだと思いこんで、Proxyがクライアントに送り込んだニセのサーバー証明書の公開鍵で通信を暗号化するので、Proxyはその内容を復号することができます。
あとは、復号したリクエストをそのまま対象のホストに転送すれば、httpsにも関わらずProxyは通信内容を把握しつつ、対象ホストとの通信を取り持つことができてしまいます。
これで中間者攻撃が成立しますʕ  ゚皿゚ ʔ 。

図にすると以下の流れとなります

f:id:yuroyoro:20180216193258p:plain

通常、このような攻撃はブラウザが警告を出すために成立しません。
まず、ユーザーが明示的にブラウザにProxyを指定する必要がありますし(port forwardを利用した透過Proxyはその限りではない)、Proxyが署名に使用するルート証明書(または中間証明書)がTrust Chainにないからです。
逆に言えば、信頼できないルート証明書をシステムにインストールしてしまうと、このような攻撃が成立する余地が生まれてしまいます。

実際に、一部のセキュリティアプライアンスやアンチウィルスソフトウェアは、このような手法でhttps通信の内容をチェックしています。
Avastを入れた状態でブラウザで証明書チェーンを確認すると、 「Avast trusted CA」という謎の認証局が出現するのはこのためです( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

以前、LenovoのPCにプリインストールされたアドウェアSuperfish」がルート証明書をシステムにインストールした上に、全PCで共通のCA秘密鍵を使っていたことで大問題になりましたね。
Dellでも似たようなことがあったみたいです( ꒪⌓꒪)

LenovoのPC全機種にプレロードされているアドウェアが実は恐ろしいマルウェアだった! | TechCrunch JapanDellのPCに不審なルート証明書、LenovoのSuperfishと同じ問題か - ITmedia エンタープライズ

実装

それでは具体的な実装の解説を行います。処理の流れは以下のとおりです。

  1. CONNECTメソッドのリクエストから、http.Hijackerを使って生のTCPコネクションを取り出す
  2. クライアントには200 okを返す
  3. 接続先ホストの証明書を、予め用意してあるroot証明書でサインして生成する
  4. 生成した証明書でクライアントとtls接続を確立する (root証明書が登録されていないとブラウザで警告が出る)
  5. goroutine起こして、クライアントとのtls接続からhttp requestを読み込む
  6. 受けたhttp requestをそのまま接続先hostに送信する
  7. 接続先hostからのhttp responseを、クライアントtls接続に書き込む
  8. EOFが来るまで 5-7繰り返し

https://github.com/yuroyoro/mitm_proxy_sample/blob/master/https.go#L57

func (proxy *MiTMProxy) mitmRequest(w http.ResponseWriter, r *http.Request) {
    // http.Hicjacker を利用してクライアントとの生のTCPコネクションを取り出す
    conn := hijackConnect(w)
    // クライアントに200 OKを返しておく
    conn.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))

    // 以降の処理はgoroutine上で行うgo proxy.transportHTTPSRequest(w, r, conn)
}

func (proxy *MiTMProxy) transportHTTPSRequest(w http.ResponseWriter, r *http.Request, conn net.Conn) {
    proxy.info("transportHTTPSRequest : %s %s", r.Method, r.URL.String())

    // 対象ホストのニセのサーバー証明書を生成して署名する
    host := r.Host
    tlsConfig, err := proxy.generateTLSConfig(host)
    if err != nil {
        if _, err := conn.Write([]byte("HTTP/1.0 500 Internal Server Error\r\n\r\n")); err != nil {
            proxy.error("Failed to write response : %v", err)
        }
        conn.Close()
    }

    // クライアントとのTCP接続上で、ニセのサーバー証明書を利用してTLS接続を待ち受ける
    tlsConn := tls.Server(conn, tlsConfig)
    if err := tlsConn.Handshake(); err != nil {
        proxy.error("Cannot handshake client %v %v", r.Host, err)
        return
    }
    defer tlsConn.Close()

    proxy.info("transportHTTPSRequest : established tls connection")

    // ニセの証明書で確立したTLS接続上でクライアントからのリクエストを読み込む
    tlsIn := bufio.NewReader(tlsConn)
    for !isEOF(tlsIn) {
        req, err := http.ReadRequest(tlsIn) // http.Requestオブジェクトとして通信を読み込むif err != nil {
            if err == io.EOF {
                proxy.error("EOF detected when read request from client: %v %v", r.Host, err)
            } else {
                proxy.error("Cannot read request from client: %v %v", r.Host, err)
            }
            return
        }

        proxy.info("transportHTTPSRequest : read request : %s %s", req.Method, req.URL.String())

        // 転送用にURLやヘッダーなどを設定
        req.URL.Scheme = "https"
        req.URL.Host = r.Host
        req.RequestURI = req.URL.String()
        req.RemoteAddr = r.RemoteAddr

        dumpRequest(req)
        removeProxyHeaders(req)

        // http.RoundTripper で受信したリクエストを対象ホストに転送し、レスポンスを受け取る
        resp, err := proxy.transport.RoundTrip(req)
        if err != nil {
            proxy.error("error read response %v %v", r.URL.Host, err.Error())
            if resp == nil {
                http.Error(w, err.Error(), 500)
                return
            }
        }

        proxy.info("transportHTTPSRequest : transport request: %s", resp.Status)

        dumpResponse(resp)

        // レスポンスをクライントへのTLS接続に書き込む
        resp.Write(tlsConn)
    }

    proxy.info("transportHTTPSRequest : finished ")
}

ポイントは、 リクエストを受けるとニセのサーバー証明書をその場で生成して、その証明書と http.Hijackerで取り出したクライントのTCPtls.Serverを用いてTLS接続をなりすますことです(生成した証明書はキャッシュします)。
証明書の生成は長くなるのでここには載せませんが、 こちらを見てもらえばと思います。

クライントとのTLS接続を乗っ取れば、あとはその接続上でHttpリクエストを読み込み、対象ホストに転送すればOKです。Goでは、 http.RoundTripperを利用すれば http.Requestをそのまま転送できるので便利です。その際に、リクエスト・レスポンスの内容をdumpしています。
悪意があれば、この段階で改竄も可能でしょう。

まとめ

以上が、Man-in-The-Middle Attackを行う簡単なProxyの実装です。この攻撃が成功する条件としては、

  • 経路上にこのようなProxyが存在する (https通信がport forwardされている場合もある)
  • Proxyが署名に使うルート証明書がTrust Chainに存在する

の2点です。特に2点目はTLSの根幹をなす部分で、それゆえにルート証明書の管理は厳格に行う必要があり、GoogleSymantecの証明書を無効にし、LenoveやDellは責められるべきなのです。Avastもちょっとどうかと思います。

実際に手を動かして実装してみると、Proxyの実装で注意スべき点や、TLS認証局の仕組みとか色々と学びがあり、よかったとおもいました( ꒪⌓꒪)

関数の話

$
0
0

こんにちは、しいたけです。

某所で関数型プログラミングとはリスト処理のことなのか、と燃えているのを見て、関数型プログラミングとは何か、ということを自分なりの考えを述べたいと思いました。春なので。

この資料は2年ほど前にSupershipの社内勉強会で使ったものですが、この中で関数とオブジェクトを対比している箇所があります。 関数もオブジェクトも、変数や関数の引数戻り値として扱える第1級の値であり、状態を持ち(メンバー変数/クロージャ)、組み合わせが可能(delegate, composition/関数合成)、である、と。

ではオブジェクト指向関数型プログラミングで何が決定的に異なるかというと、設計・実装のアプローチに何を中心に据えるか、ということだと思います。

オブジェクト指向では、クラス・オブジェクトをモデリングし、各種のオブジェクト指向デザインパターンを用いてオブジェクト同士を組み合わせながら設計・実装を行います。 関数型プログラミングでは、関数を細かな組み合わせ可能な単位に分解し、関数合成、再帰クロージャなどを駆使しながら組み合わせることで設計・実装します。

こう書いてみると当たり前のことですが、コードの記述スタイルだけ議論しても意味がなくて、そのような記述はプログラム全体を貫く思想のなかでどのような位置づけにあるのかのコンテキストが重要なのではないでしょうか。 例えば、Goでは無理して無名関数を用いたリスト処理を書くより、forで書くほうがはるかに自然ですよね。 JavaScriptでは……どうでしょうか、色々なスタイルで書けるので議論が紛糾するのかも。

map/reduceを使ったリスト処理が関数型プログラミングの全てかというとそうではなく、あくまで関数を中心に据えた考えた方の一つとして、遅延評価 + 高階関数/クロージャの組み合わせによるストリームライクな実装がある、ということだと思います。 関数を中心に据えると、「何をフィルターするか」「要素をどのように変換するか」という処理の単位が関数として抽出され、その組み合わせ方法として遅延評価リストと高階関数を用いる方法がある、というだけです。 結果、関数を中心にすえたアプローチではmap/reduceを使う記述が自然と導かれます。 とはいえ、 リスト処理の実装としては再帰を使う方法もあるので、あくまで関数型プログラミングによるリスト処理の1アプローチにすぎません。

どちらのスタイルで書くのがよいか、というのに絶対的な正解はないと思いますが、関数を中心としたアプローチを知っていると、手続き型のプログラムを書いていても思わぬ場面で役に立つことがあります。 ちょうど最近役にたった実体験のひとつで、 ある rspecのパフォーマンスチューニングの際に遅延評価が役にたったことがあります。

error_msg = generate_error_message(actual_value, expected_value)
expect(actula_value).to have_content(expected_value),  error_msg

こんな感じのrspecのコードで、 assertのエラー時に表示する error_msgを生成する generate_error_messageが非常にコストの掛かる処理でした。 通常はassertに失敗する場合の方が稀なので、必要になるまで(assertに失敗するまで) この処理の呼び出しを遅延させることで性能が改善しそうです。

rspecexpectはエラーメッセージの代わりに Procを渡すことが可能です。 そこで、エラーメッセージを生成する処理をクロージャとして抽出し、 expectに渡すことで遅延評価させることができます。

error_msg_generator = ->() { generate_error_message(actual_value, expected_value) }
expect(actula_value).to have_content(expected_value),  error_msg_generator

関数型プログラミングの知識があると、 expectの引数が Procを取る、というシグニチャから、遅延評価による性能改善に利用する発想が導かれます。 このように、関数型プログラミングは通常の手続き型プログラミングでもおおいに役立ちますので、双方それぞれの手法を学んでおくとプログラミングの裾野が広がりま。 これが、ぼくが関数型プログラミングを学ぶことをオススメする理由です。

まとめ

しいたけおいしいです。

async/awaitとpromise使えばモナド糖衣構文っぽいの書けそうだよねって思って書いてみたけど、async () => {} でwrapしないといけないしまぁそんなにきれいに書けなかったって話

$
0
0

タイトルで全部言い切ってますが

// Optional container like maybe monad class Option {
  constructor(value){this.value = value
  }

  async promise() {returnnew Promise((resolve, reject) => {if (this.value) {
        resolve(this.value);
      }else{
        reject(undefined);
      }});
  }}

こんなMaybe とかOptionalっぽいやつを用意します。 promise()で Promiseを生成して返すようにします。もってる値に応じて resolve (JustやSome)したり reject (Nothing)したりします。

const o1 = new Option("foo");
const o2 = new Option("bar");
const o3 = new Option(null);

(async () => {try{const v1 = await o1.promise();
    const v2 = await o2.promise();

    console.log(v1, v2);
  }catch{}})(); // => foo bar

(async () => {try{const v1 = await o1.promise();
    const v2 = await o2.promise();
    const v3 = await o3.promise();

    console.log(v1, v2, v3);
  }catch{}})(); // do nothing// introduce doM emulates do-like syntax sugar 
async function doM(f1, f2) {try{return f1();
  }catch{if (f2) {return f2();
    }}}// => foo bar

asyncの中で、 promise()を awaitで呼び出すことで、 do記法っぽいあれ……に見えなくもなくないコードになりました。でも失敗系処理しない場合でも catch書く必要あるのダルいですね? try..catchを変わりにやってくれるヘルパーを導入しましょう

// introduce doM emulates do-like syntax sugar 
async function doM(f1, f2) {try{return f1();
  }catch{if (f2) {return f2();
    }}}

この doMに asyncな無名関数を渡すと、その中でdo記法っぽいあれ……に見えなくもなくないコードを書けます。

doM(async () => {const v1 = await o1.promise();
  const v2 = await o2.promise();

  console.log(v1, v2);
}); // => foo bar

doM(async () => {const v1 = await o1.promise();
  const v2 = await o2.promise();
  const v3 = await o3.promise();

  console.log(v1, v2, v3);
}); // do nothing

だからどうしたって話ですが、いろいろなモナドを作って合成して扱う場合に便利かもしれないですがそもそもモナドとかJavaScriptで作るな。以上

ラズパイとWebRTCで動物の死活監視ができるようにした話

$
0
0

こんにちわ、しいたけです。

今は夏休みで奥さんと子どもたちが帰省しているので、動物と2人で暮らしています。

で、外出すると動物だけを家に残していくことになります。 ペットモニターとか市販でもあるんですが、せっかくなので、 夏休みの自由研究として、ラズパイ+カメラモジュールとWebRTCを使って、外出先からでも動物の状態を確認できるやつを作ってみました。

f:id:yuroyoro:20190812154310j:plain

↑ 死活監視される動物の様子です

用意したもの

ラズパイ3とケースのセットとカメラモジュールは Raspberry Pi Shop by KSYで購入。期間限定セールでちょっと安かったです。 このセットについてるケースは、ラズパイのマークの穴にカメラモジュールを固定することができて便利。 時雨堂のmomoだと、Raspberry PiGPUH.264ハードウェアエンコーダー機能を利用できるらしいので、ラズパイzeroでも行けるらしいです。

で、ラズパイを三脚とスマホ用アダプタで固定します。こんな感じになる。

f:id:yuroyoro:20190814165439j:plain

ラズパイの初期設定

まずはラズパイの初期設定でOSインストール。購入したキットのSDカードにすでにOSが用意されているので、電源入れて画面の通りにセットアップすれば終了。

Raspberry Pi 3 Model B+の初回セットアップ(購入から起動まで) - Qiita

あとは、sshとカメラモジュールをconfigから有効にしておく。

Raspberry Piカメラのセットアップ方法

最後に、mDNSとssh周りの設定を行って、GUIをオフにして完了。これで手元のmacbookからsshで接続して作業できるようになった。

手持ちのRaspberryPiをサクッとmDNSに対応させる - Qiita

WebRTC Native Client Momo

時雨堂の WebRTC Native Client Momoを使います。あっさり接続できて最高でした。

Githubのリリースページから最新のバイナリがtarで配布されているので、ダウンロードして解凍するだけです。

必要なライブラリを入れて、

$ sudo apt-get install libnspr4 libnss3

あとはバイナリを実行するだけで、ラズパイのカメラモジュールを利用したストリーミングが可能になります。

$ ./momo --no-audio ayame wss://ayame-lite.shiguredo.jp/signaling <your-github-id>@<your-room-name> --signaling-key <your-signaling-key>

起動時のオプションは、シグナリングサーバーにAyameとそのURL、そしてルームIDを指定します。また、次で説明するシグナリングキーもわたします。

時雨堂 WebRTC シグナリングサービス Ayame Lite

WebRTCにおいてNAT超えで通信を行うにあたって、シグナリングサーバを介してそれぞれのピアの接続の調整を行う必要がある。 時雨堂がWebRTC の P2P利用向けに無料で利用できるシグナリングサービスを提供してくれているので、ありがたく使わせていただきます。

WebRTC シグナリングサービス Ayame Lite 開発ログ

P2Pで接続できる場合には、特に登録も必要なく使えます。4G回線の場合などTURNサーバーを経由する必要がある場合は、ベータテスト中のシグナリングキーを利用してTURNサーバーの払い出しをしてもらう必要があります。 Githubアカウントでサインアップすると、シグナリングキーが発行されてTURN サーバの利用が可能になります。

STUN/TURNについてはこのあたり参照

4Gでつながらないとtwitterで囀っていたところ、 @voluntasにベータ版使ってみる? とお誘いを受けたので、ありがたく使わせてもらいました。 ちょうどオープンベータが始まったところでした。

Ayame Lite オープンベータテスト開始しました - shiguredo - Medium

こちらからGithubアカウントでサインアップできます。

Ayame Lite

時雨堂最高では?

クライアント側の実装

あとは、クライアントを準備して接続です。手っ取り早く試してみるのに、 OpenAyame/ayame-react-sample: Ayame React サンプルを利用しました。 上記のリポジトリgit cloneしたあと、 yarn installして yarn serveすると、localhostで動作確認ができます。

こんな感じで、AyameのURLと、自分で指定したルームID、シグナリングキーを入力して接続を押すと、ストリーミングでラズパイ側のカメラから配信されていることが確認できます。

f:id:yuroyoro:20190814182922p:plain

iOSSafariの場合は再生ボタンを押す必要があるとのことで、videoタグに controlsを追加して対応しました。

<Videos>
  <RemoteVideo ref={remoteVideoRef} muted autoPlay controls/>
</Videos>

その他調整

まず、クライアントのアセット一式を適当な場所から配信できるようにします。 yarn buildしてできた成果物をさくらVPSにnginxを立ててレッツをエンクリプトしてhttpsで配信できるようにしました。 httpsで配信できる場所なら、cloudfrontとかNetlifyとかでもいいと思います。

あとは、ラズパイ側のmomoをsystemdのサービスとして設定して起動時に起きるようにすれば終わりです。

Raspberry Pi で systemd を使ってプログラムを自動実行する - Qiita

まとめ

ラズパイでWebRTCするにあたって、当初は NTTコムさんの提供する Skywayとそのsdkを試してみたのですが、どうもデモが上手く動作しませんでした。 時雨堂のmomoとOpenAyameを使ってみたところ、バイナリを落としてきてドキュメントの手順通りに進めるだけであっさり接続できてしまいました。

これで外出時でも動物の様子を死活監視ができて捗る。

時雨堂最高では?

Viewing all 61 articles
Browse latest View live