Go 言語の列挙型的な定数と定数でのフラグ管理
列挙型的定数
- Go では const と iota を使って列挙型的な定数を定義する
- iota は const 宣言開始時に 0 になり、定数を定義する毎にインクリメントする
- 定数定義は値を指定しないと直前の定数と同じ値になるので iota と組み合わせることで連続した値が定義される
// 定義 type Flag int const ( // iota = 0 (仮想コード) Flag1 Flag = iota // Flag1 = iota; iota++ Flag2 // Flag2 = iota; iota++ Flag3 // Flag3 = iota; iota++ ) // 利用例 f := Flag1 if f == Flag1 { // Flag1 の場合の処理 }
列挙型的定数を使ったフラグ管理
- 列挙型的な定数の定義時に一工夫すると定数をフラグ的に管理できるようになる
// 定義 type Flag int const ( Flag1 Flag = 1 << iota // 1 << 0 (10進数: 1, 2進数: 00000001) Flag2 // 1 << 1 (10進数: 2, 2進数: 00000010) Flag3 // 1 << 2 (10進数: 4, 2進数: 00000100) Flag4 // 1 << 3 (10進数: 8, 2進数: 00001000) ) // 利用例 f := Flag1 if f & Flag1 != 0 { // Flag1 の場合の処理 // &(and) 演算子で双方にビットが立っていた場合に 0 以外が返るので Flag1 が有効かどうかの判定ができる } if f & (Flag1 | Flag2) != 0 { // Flag1 か Flag2 の場合の処理 // |(or) 演算子で Flag1 かつ Flag2 の値を作ることができるので複数のフラグの判定処理がちょっと楽になる }
ひとつの定数で複数種類のフラグを管理する
- 例えば文字の色と書体を同一の方で管理するような場合
type Flag int const Flag_N_Start = 0 const ( Flag1 Flag = 1 << (iota + Flag_N_Start) // 1 (00000001) Flag2 // 2 (00000010) Flag3 // 4 (00000100) Flag_N_Bits = iota // 3 (Flag_N で 3bit 使っている) Flag_N_Mask = 1 << Flag_N_Bits - 1 // 7 (00000111) (Flag_N 用のビットマスク) ) const Flag_A_Start = Flag_N_Bits // Flag_A の開始ビット const ( FlagA Flag = 1 << (iota + Flag_A_Start) // 1 << (0 + 3) = 8 (00001000) FlagB // 1 << (1 + 3) = 16 (00010000) FlagC // 1 << (2 + 3) = 32 (00100000) Flag_A_Bits = iota // 3 (Flag_A で 3bit 使っている) Flag_A_Mask = (1 << Flag_A_Bits - 1) << Flag_A_Start // 56 (00111000) (Flag_A 用のビットマスク) (使用ビット分だけマスクを作って開始ビット分だけ左シフトする) ) f := (Flag1 | FlagB) if f & (Flag1 | FlagA) != 0 { // Flag1 か Flag2 だった時の処理 }
termbox-go 使い方まとめ
はじめに
https://github.com/kurehajime/pong-command とかみたいな感じでターミナル上で任意の座標に任意の文字を表示できる termbox-go の使い方まとめ。
概要としては termbox の内部でターミナル上の座標に対応した文字のバッファを持っているので、SetCell で座標と文字などを指定して、Flush で表示するという感じ。
使い方
インストール
go get github.com/mattn/go-runewidth // termbox-go が参照している go get github.com/nsf/termbox-go
初期化
err := termbox.Init() if err != nil { panic(err) } defer termbox.Close()
バッファのクリア
文字色と背景色を指定してバッファをクリアする
色の一覧は https://godoc.org/github.com/nsf/termbox-go#Attribute
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
文字のセット
https://godoc.org/github.com/nsf/termbox-go#SetCell
termbox.SetCell(1, 2, rune("a"), termbox.ColorDefault, termbox.ColorDefault)
文字の取得
termbox 内部のバッファから文字を取得する。
表示済みの文字の色だけ変える場合などに使用できる。
以下は2行目の3文字目を取得する。
width, _ := termbox.Size() row := 1 col := 2 cell := termbox.CellBuffer()[(width*row)+col] var char rune = cell.Ch var fg termbox.Attribute = cell.Fg var bg termbox.Attribute = cell.Bg
バッファの表示
termbox.Flush()
キーボード入力の受付
mainloop: for { switch ev := termbox.PollEvent(); ev.Type { case termbox.EventKey: if ev.Key == termbox.KeyEsc { break mainloop // Esc で実行終了 } case termbox.EventResize: // 画面のリサイズが発生したので再描画などをする } }
マウス入力の受付
mac の Terminal.app や iTerm2.app では現状バグが有って正常に動かない様子(マウス入力時に Esc など他のキー押下イベントが発生してしまう)
https://github.com/nsf/termbox-go/issues/120
termbox.SetInputMode(termbox.InputEsc | termbox.InputMouse) mainloop: for { switch ev := termbox.PollEvent(); ev.Type { case termbox.EventMouse: if ev.Key == termbox.MouseLeft { mx := ev.MouseX my := ev.MouseY } }
キーボードのアルファベットキーを取得する
- termbox のデフォルトのキーイベントではアルファベットキーをそのままは受け取れないので
github.com/jteeuwen/keyboard/termbox
を使う
import keyboard "github.com/jteeuwen/keyboard/termbox" func main() { // termbox の初期化などは省略 pollEvent() } func pollEvent() { running := true kb := keyboard.New() kb.Bind(func() { running = false }, "escape", "q") kb.Bind(func() { /* do up */ }, "up", "k") kb.Bind(func() { /* do down */ }, "down", "j") kb.Bind(func() { /* do left */ }, "left", "h") kb.Bind(func() { /* do right */ }, "right", "l") for running { kb.Poll(termbox.PollEvent()) } }
文字色を数字で指定する
- 文字色や背景色は termbox.Attribute 型にキャストすれば数字で直接指定できる
// 256色表示(デフォルトは8色) termbox.SetOutputMode(termbox.Output256) // 文字色を背景色を数字で指定(内部で-1されているので+1する) termbox.SetCell(0, 0, "a", termbox.Attribute(100+1), termbox.Attribute(200+1))
参考アプリケーション
- termbox-go を使って実装されているアプリケーションを参考のためにいくつか
Go 言語でブログを作ったメモ
- Go 言語学習のためにユーザー認証とDB連携のあるブログアプリを作った
- ソースは https://github.com/nirasan/go-blog
- Web 周りに Gorilla web toolkit を、ORM に gorp を使用
- Go 自体シンプルだし、あえてシンプルなライブラリを選んでみたのでわりとはまらずにできた
- 次は revel とかリッチめのライブラリ試すかこのままシンプルで行くかどうするか
Go 言語で gorilla/mux, gorilla/context, gorilla/sessions を使ったユーザーログイン機能のサンプル
はじめに
Go 言語練習のため gorilla/mux, gorilla/context, gorilla/sessions を使ったユーザーログイン機能を実装してみました。
ユーザーの管理はメモリ上の map 型変数にユーザー名とパスワードを入れているだけなので、ここをDBに変えるのが次の課題のつもりです。
package main import ( "net/http" "html/template" "github.com/gorilla/mux" "github.com/gorilla/context" "github.com/gorilla/securecookie" "github.com/gorilla/sessions" "errors" ) var ( // セッションストアの初期化 store *sessions.CookieStore = sessions.NewCookieStore(securecookie.GenerateRandomKey(64)) // 登録ユーザーをメモリ上で管理 users map[string]string = make(map[string]string) ) const ( SessionName = "session-name" ContextSessionKey = "session" ) func main() { r := mux.NewRouter() handleFunc(r, "/", rootHandler) handleFunc(r, "/register", registerGetHandler).Methods("GET") handleFunc(r, "/register", registerPostHandler).Methods("POST") handleFunc(r, "/login", loginGetHandler).Methods("GET") handleFunc(r, "/login", loginPostHandler).Methods("POST") handleFunc(r, "/logout", logoutGetHandler).Methods("GET") handleFunc(r, "/logout", logoutPostHandler).Methods("POST") http.ListenAndServe(":8080", r) } // アプリケーション共通処理を常に呼び出すための糖衣構文 func handleFunc(r *mux.Router, path string, fn http.HandlerFunc) *mux.Route { return r.HandleFunc(path, applicationHandler(fn)) } // アプリケーション共通処理 func applicationHandler(fn http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // セッションの取得 session, err := store.Get(r, SessionName) if err != nil { // 不正なセッションだった場合は作り直す session, err = store.New(r, SessionName) checkError(err) } context.Set(r, ContextSessionKey, session) // 個別のハンドラー呼び出し fn(w, r) } } // トップページ func rootHandler(w http.ResponseWriter, r *http.Request) { type page struct { Username string } p := &page{} session, err := getSession(r) checkError(err) if v, ok := session.Values["username"]; ok { p.Username = v.(string) } executeTemplate(w, "template/index.html", p) } // 登録ページ func registerGetHandler(w http.ResponseWriter, r *http.Request) { executeTemplate(w, "template/register.html", nil) } // 登録処理 func registerPostHandler(w http.ResponseWriter, r *http.Request) { // フォームのパース r.ParseForm() // フォームの値の取得 username, password := r.Form["username"][0], r.Form["password"][0] // 空なら登録ページへ if username == "" || password == "" { http.Redirect(w, r, "/register", http.StatusFound) return } // ユーザー登録 users[username] = password // 現在のユーザーをセッションで管理 session, err := getSession(r) checkError(err) session.Values["username"] = username session.Save(r, w) // トップページへ http.Redirect(w, r, "/", http.StatusSeeOther) } // ログインページ func loginGetHandler(w http.ResponseWriter, r *http.Request) { executeTemplate(w, "template/login.html", nil) } // ログイン処理 func loginPostHandler(w http.ResponseWriter, r *http.Request) { // フォームのパース r.ParseForm() // フォームの値の取得 username, password := r.Form["username"][0], r.Form["password"][0] // 空ならログイン画面へ if username == "" && password == "" { http.Redirect(w, r, "/login", http.StatusFound) return } // ユーザーが登録済みでありパスワードが一致したらログイン状態に if v, ok := users[username]; ok && v == password { session, err := getSession(r) checkError(err) session.Values["username"] = username session.Save(r, w) http.Redirect(w, r, "/", http.StatusSeeOther) } else { http.Redirect(w, r, "/login", http.StatusSeeOther) } } // ログアウトページ func logoutGetHandler(w http.ResponseWriter, r *http.Request) { executeTemplate(w, "template/logout.html", nil) } // ログアウト処理 func logoutPostHandler(w http.ResponseWriter, r *http.Request) { session, err := getSession(r) checkError(err) delete(session.Values, "username") session.Save(r, w) http.Redirect(w, r, "/", http.StatusSeeOther) } // セッションの取得 func getSession(r *http.Request) (*sessions.Session, error) { if v := context.Get(r, ContextSessionKey); v != nil { return v.(*sessions.Session), nil } return nil, errors.New("failed to get session") } // テンプレートの実行 func executeTemplate(w http.ResponseWriter, name string, data interface{}) { t, err := template.ParseFiles(name) checkError(err) err = t.Execute(w, data) checkError(err) } // エラーチェック func checkError(err error) { if err != nil { panic(err) } }
おわりに
net/http のハンドラーをチェインさせて共通処理を切り出すパターンは「Go言語によるWebアプリケーション開発」読んだらちゃんと書いてあったのでちゃんと読もうと思いました。
学習目的で gorilla/mux で実装してみてて実際勉強になってますが、普通に web アプリ作るならたぶん goji とか WAF を使った方がいい気がしてきてます。
Go 言語で gorilla/mux を使った簡単なウェブアプリのサンプル
はじめに
Go 言語で簡単なウェブアプリを作りたいので軽量なウェブツールキット gorilla/mux を使ったサンプル。
context の使い方は gin や goji のミドルウェアの実装を参考に、共通化したい処理を個別のハンドラの上にラップする形にした。おきまりのパターンっぽいので Usage とかに書いて欲しい。
package main import ( "fmt" "net/http" "github.com/gorilla/mux" "github.com/gorilla/context" ) func main() { r := mux.NewRouter() // 単純なハンドラ r.HandleFunc("/", YourHandler) // パスに変数を埋め込み r.HandleFunc("/hello/{name}", VarsHandler) // パス変数で正規表現を使用 r.HandleFunc("/hello/{name}/{age:[0-9]+}", RegexHandler) // クエリ文字列の取得 r.HandleFunc("/hi/", QueryStringHandler) // 静的ファイルの提供 // $PROROOT/assets/about.html が http://localhost:8080/assets/about.html でアクセスできる r.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(http.Dir("./assets")))) // リダイレクト r.HandleFunc("/moved", RedirectHandler) // マッチするパスがない場合のハンドラ r.NotFoundHandler = http.HandlerFunc(NotFoundHandler) // 複数のハンドラで共通の処理を実行する // 今回はcontextのセットとゲットを試しているが、同じパターンでDBの初期化や認証処理やログ書き出しなどにも応用できる // ハンドラを引き渡すには http.Handler 型を使い func(http.ResponseWriter, *http.Request) から http.Handler への変換には http.HandlerFunc を利用する // http.Handler をハンドラとして登録する場合は Router.Handle を利用する r.Handle("/some1", UseContext(http.HandlerFunc(SomeHandler1))) r.Handle("/some2", UseContext(http.HandlerFunc(SomeHandler2))) // http://localhost:8080 でサービスを行う http.ListenAndServe(":8080", r) } func YourHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Gorilla!\n")) } func VarsHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) fmt.Fprintf(w, "%s Loves Gorilla\n", vars["name"]) } func RegexHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) fmt.Fprintf(w, "%s is %s years old\n", vars["name"], vars["age"]) } func QueryStringHandler(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() fmt.Fprintf(w, "%s Loves Gorilla\n", q.Get("name")) } func RedirectHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusFound) } func NotFoundHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Gorilla!\nNot Found\n")) } const MyContextKey = 1 func UseContext(handler http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { context.Set(r, MyContextKey, "Call SomeMiddleware") handler.ServeHTTP(w, r) } return http.HandlerFunc(fn) } func SomeHandler1(w http.ResponseWriter, r *http.Request) { contextVal := context.Get(r, MyContextKey) fmt.Fprintf(w, "%s Call SomeHandler1", contextVal) } func SomeHandler2(w http.ResponseWriter, r *http.Request) { contextVal := context.Get(r, MyContextKey) fmt.Fprintf(w, "%s Call SomeHandler2", contextVal) }
Mac + DockerToolbox 環境の Centos6 で Supervisor を使って複数のサービスを起動する 〜 sshd と named を起動する例
はじめに
Dockerfile の用意
FROM centos:6 ### sshd のインストール RUN yum -y install initscripts MAKEDEV RUN yum check RUN yum -y update RUN yum -y install openssh-server passwd ### sshd の設定 RUN sed -ri 's/^#PermitEmptyPasswords no/PermitEmptyPasswords yes/' /etc/ssh/sshd_config RUN sed -ri 's/^#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed -ri 's/^UsePAM yes/UsePAM no/' /etc/ssh/sshd_config ### sshd の起動準備 RUN /etc/init.d/sshd start RUN /etc/init.d/sshd stop ### パスワードなしで接続 RUN passwd -d root ### named のインストール RUN yum install -y bind RUN /etc/init.d/named start RUN /etc/init.d/named stop ### Supervisor のインストール RUN yum install -y wget RUN wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | python RUN easy_install supervisor COPY supervisord.conf /etc/supervisord.conf CMD /usr/bin/supervisord -c /etc/supervisord.conf
Supervisor の設定ファイルの用意
- Dockerfile と同じディレクトリに supervisord.conf として用意
[inet_http_server] port=127.0.0.1:9001 [supervisord] logfile=/var/log/supervisord.log ; (main log file;default $CWD/supervisord.log) logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) logfile_backups=10 ; (num of main logfile rotation backups;default 10) loglevel=info ; (logging level;default info; others: debug,warn) pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) nodaemon=true ; (start in foreground if true;default false) minfds=1024 ; (min. avail startup file descriptors;default 1024) minprocs=200 ; (min. avail process descriptors;default 200) [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [supervisorctl] serverurl=http://127.0.0.1:9001 [program:sshd] command=/usr/sbin/sshd -D autostart=true autorestart=true [program:named] command=/usr/sbin/named -u named -f process_name=%(program_name)s numprocs=1 directory=/var/named priority=100 autostart=true autorestart=true startsecs=5 startretries=3 exitcodes=0,2 stopsignal=TERM stopwaitsecs=10 redirect_stderr=false stdout_logfile=/var/log/named_supervisord.log stdout_logfile_maxbytes=1MB stdout_logfile_backups=10 stdout_capture_maxbytes=1MB
イメージの作成
$ docker build -t centos:supervisor .
コンテナの起動
$ docker run -p 20022:22 -p 20053:53 -d centos:supervisor
接続確認
$ ssh -p 20022 root@127.0.0.1
サービスの管理
- コンテナ上で実行
$ supervisorctl status #=> サービスの確認 $ supervisorctl restart named #=> サービスの再起動
Mac + DockerToolbox で ssh 可能な CentOS のイメージを作成する
DockerToolbox のインストール
http://docs.docker.com/mac/step_one/ の手順に従ってインストール
Docker Quickstart Terminal の実行
Dockerfile の作成
- Docker 用のディレクトリを作成して Dockerfile の作成
$ mkdir -p /path/to/docker/centos-ssh $ cd /path/to/docker/centos-ssh
FROM centos:6 RUN yum -y install initscripts MAKEDEV RUN yum check RUN yum -y update RUN yum -y install openssh-server passwd # 空パスワードの場合は以下をコメントアウト RUN sed -ri 's/^#PermitEmptyPasswords no/PermitEmptyPasswords yes/' /etc/ssh/sshd_config RUN sed -ri 's/^#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed -ri 's/^UsePAM yes/UsePAM no/' /etc/ssh/sshd_config RUN /etc/init.d/sshd start RUN /etc/init.d/sshd stop # 空パスワードの場合は以下をコメントアウト RUN passwd -d root # 任意のパスワードの場合は以下をコメントアウト & パスワードを書き換える # RUN echo 'root:root' | chpasswd EXPOSE 22 CMD /usr/sbin/sshd -D
イメージの作成
$ docker build -t centos-ssh .
コンテナの起動
$ docker run -p 20022:22 -d centos-ssh
ポートフォワーディングの設定
- コンテナ起動時に -p オプションで、コンテナから docker-machine の間のポートフォワーディングの設定を行った
- Mac の場合はさらに、docker-machine と Mac の間のポートフォワーディングの設定を行う必要がある
手順
- アプリケーションから VirtualBox の起動
- "default" を選択して "設定" ボタンを押下
- "ネットワーク" を選択して "ポートフォワーディング" を押下
- ポートフォワード設定の追加
ssh 接続実行
$ ssh -p 20022 root@127.0.0.1