supervisorの起動スクリプト
supervisorの最新版をeasy_installでインストールしたら起動スクリプトがなかったので簡単なやつを書いた。centos6.7で /etc/init.d/supervisord として配置する。
#!/bin/sh # # supervisord - this script starts and stops supervisord # # chkconfig: 35 80 20 # description: Autostart supervisord. # Source function library. . /etc/rc.d/init.d/functions supervisorctl="/usr/bin/supervisorctl" supervisord="/usr/bin/supervisord" name="supervisor" [ -f $supervisord ] || exit 1 [ -f $supervisorctl ] || exit 1 RETVAL=0 start() { echo -n "Starting $name: " $supervisord -c /etc/supervisord.conf RETVAL=$? if [ $RETVAL -eq 0 ] then echo -e "SUCCESS" else echo -e "FAILED" fi return $RETVAL } stop() { echo -n "Stopping $name: " $supervisorctl shutdown RETVAL=$? if [ $RETVAL -eq 0 ] then echo -e "SUCCESS" else echo -e "FAILED" fi return $RETVAL } case "$1" in start) start ;; stop) stop ;; restart) stop start ;; status) $supervisorctl status ;; esac exit $RETVAL
参考
deferでのrecoverのよくわからない挙動がよくわからない
deferで呼び出した関数でrecoverした場合にはpanicは発生しない。
https://play.golang.org/p/Z0oJ2I26wq8
package main import "fmt" func main() { defer recoverFunc() panic("Panic happens!") } func recoverFunc() { if err := recover(); err != nil { fmt.Printf("Recover: %v¥n", err) } }
しかし、deferで呼び出した関数からさらに呼び出した関数でrecoverした場合にはpanicが発生する(recoverが働かない)。
https://play.golang.org/p/FTqz736dHD9
package main import "fmt" func main() { defer deferredFunc() panic("Panic happens!") } func deferredFunc() { recoverFunc() } func recoverFunc() { if err := recover(); err != nil { fmt.Printf("Recover: %v¥n", err) } }
直感的に後者の場合でもpanicが発生しないかと思ったんだけどな。
Goでgraceful restartをミニマムに書く
前回の記事でgraceful shutdownをミニマムに実装した。その続きでgraceful restartをミニマムに標準パッケージのみで実装してみる。
参考にしたのは以下のパッケージ。
実装
前回と同様、理解のために最低限の実装を行なっている。エラー処理もところどころ真面目にやっていない。
package main import ( "fmt" "log" "net" "net/http" "os" "os/exec" "os/signal" "strconv" "sync" "syscall" "time" ) // indexHandler ... func indexHandler(w http.ResponseWriter, r *http.Request) { time.Sleep(10 * time.Second) w.Write([]byte("Hello, middleware!\n")) } func main() { http.HandleFunc("/", indexHandler) srv := &http.Server{Addr: ":8888", Handler: http.DefaultServeMux} var wg sync.WaitGroup srv.ConnState = func(conn net.Conn, state http.ConnState) { switch state { case http.StateActive: log.Println("StateActive!!!") wg.Add(1) case http.StateIdle: log.Println("StateIdle!!!") wg.Done() } } if isMaster() { log.Printf("master pid: %d\n", os.Getpid()) laddr, _ := net.ResolveTCPAddr("tcp", "localhost:8888") l, _ := net.ListenTCP("tcp", laddr) log.Println(l) supervise(l) } log.Printf("worker pid: %d\n", os.Getpid()) fdStr := os.Getenv("__MASTER__") fd, _ := strconv.Atoi(fdStr) file := os.NewFile(uintptr(fd), "listen socket") defer file.Close() l, _ := net.FileListener(file) waitSignal(l) err := srv.Serve(l) wg.Wait() if err != nil { log.Fatal(err) } } func waitSignal(l net.Listener) { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT) go func() { sig := <-c switch sig { case syscall.SIGINT: signal.Stop(c) l.Close() } }() } func isMaster() bool { return os.Getenv("__MASTER__") == "" } func supervise(l *net.TCPListener) { p, _ := forkExec(l) c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGUSR2) for { switch sig := <-c; sig { case syscall.SIGUSR2: new, _ := forkExec(l) p.Signal(syscall.SIGINT) p.Wait() p = new } } } func forkExec(l *net.TCPListener) (*os.Process, error) { progName, err := exec.LookPath(os.Args[0]) if err != nil { return nil, err } pwd, err := os.Getwd() if err != nil { return nil, err } f, err := l.File() if err != nil { return nil, err } defer f.Close() files := []*os.File{os.Stdin, os.Stdout, os.Stderr, f} fdEnv := fmt.Sprintf("%s=%d", "__MASTER__", len(files)-1) return os.StartProcess(progName, os.Args, &os.ProcAttr{ Dir: pwd, Env: append(os.Environ(), fdEnv), Files: files, }) }
動作確認すると、
// server $ go run test.go // server起動 // handlerを修正: "Hello, middleware!\n" => "Hello, middleware!!!!!!\n" // client1 $ curl localhost:8888 // server master process にUSR2シグナルを送る $ kill -USR2 xxxxx // client2 $ curl localhost:8888 // client1 $ curl localhost:8888 Hello, middleware! // 修正前のレスポンスが返る // client2 $ curl localhost:8888 Hello, middleware!!!!!! // 修正後のレスポンスが返る // server // 停止することなく動いたまま
感想
ポイントは
- 再起動のシグナルを受け取るmaster processと実際のサーバ処理を行うworker processを用意すること
- master processは最初に作成したリスナーをworker process作成時に渡し、masterとworkerでリスナーを共有すること
- master processは再起動のシグナルを受け取ったら新しいworker processを作成してから古いworker processにシグナルを送りgraceful shutdownをさせること
だろうか。ソケットまわりを勉強して「ソケットもファイルも同じインターフェースで処理できる」ということを知っていたが理解はしていなかった。しかし、実際に標準netパッケージのfunc (*TCPListener) Fileやfunc FileListenerを使用してリスナーとファイルの相互変換を実装したことで少しは理解できたかなと感じた。あ、リスナーとソケットは同じ意味くらいの認識でいます。 また、masterとworkerでソケットを共有するために、func StartProcessをうまく利用する実装方法を知ることができて勉強になった。
なお、masterとworkerでソケットを共有するというのは以下のブログにある図が非常にわかりやすかった。
Goでgraceful shutdownをミニマムに書く
graceful shutdownの仕組みがわかってなかったので自分でミニマムに実装してみる。
コード量が小さく読みやすかったので以下のパッケージを参考にした。
実装
自分でミニマムに実装したコードは以下の通り。アクティブな接続が返ってこなかった場合とかも考えなくてはいけないが、ここでは理解のため手元で動かして簡単に試す程度の実装をしている。
package main import ( "log" "net" "net/http" "os" "os/signal" "sync" "syscall" "time" ) func indexHandler(w http.ResponseWriter, r *http.Request) { time.Sleep(10 * time.Second) w.Write([]byte("Hello, middleware!\n")) } func main() { http.HandleFunc("/", indexHandler) srv := &http.Server{Addr: ":8888", Handler: http.DefaultServeMux} var wg sync.WaitGroup srv.ConnState = func(conn net.Conn, state http.ConnState) { switch state { case http.StateActive: log.Println("StateActive!!!") wg.Add(1) case http.StateIdle: log.Println("StateIdle!!!") wg.Done() } } l, err := net.Listen("tcp", ":8888") if err != nil { log.Fatal(err) } waitSignal(l) err = srv.Serve(l) wg.Wait() if err != nil { log.Fatal(err) } } func waitSignal(l net.Listener) { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT) go func() { sig := <-c switch sig { case syscall.SIGINT: signal.Stop(c) l.Close() } }() }
動作確認すると、
// server $ go run test.go // サーバ起動 // client $ curl localhost:8888 // curl実行 // server $ go run test.go 2017/12/20 11:10:03 StateActive!!! 2017/12/20 11:10:13 StateIdle!!! // 特に何もしない // client $ curl localhost:8888 Hello, middleware! // 正常にレスポンスが返る // client $ curl localhost:8888 Hello, middleware! $ curl localhost:8888 // 再度curl実行 // server $ go run test.go 2017/12/20 11:10:03 StateActive!!! 2017/12/20 11:10:13 StateIdle!!! 2017/12/20 11:11:59 StateActive!!! ^C // Ctrl+CでSIGINTを送る // client $ curl localhost:8888 Hello, middleware! $ curl localhost:8888 Hello, middleware! // 正常にレスポンスが返る // server $ go run test.go 2017/12/20 11:10:03 StateActive!!! 2017/12/20 11:10:13 StateIdle!!! 2017/12/20 11:11:59 StateActive!!! ^C2017/12/20 11:12:09 StateIdle!!! // 既に確立されたコネクションは正常にレスポンスを返している 2017/12/20 11:12:09 accept tcp [::]:8888: use of closed network connection exit status 1 // 確立されたコネクションが全てレスポンスを返してからサーバシャットダウンしている
感想
ポイントは
- プロセスを終了するシグナルを
signal.Notify
で捕まえて処理を変更すること http.StateActive
な接続が存在する場合はプロセスがすぐに終了しないようにすること
だろうか。最初はリスナーをクローズすると確立されたConnも終了してしまうのかと思っていたが、ここに関してはTCPListener.Close
がうまい感じにやってくれている。このためリスナーをクローズしたタイミングで新規の接続は受け付けないといったことも実現できている。
https://golang.org/pkg/net/#TCPListener.Close
Close stops listening on the TCP address. Already Accepted connections are not closed.
Goアプリケーションをsupervisorでdaemonとして起動する
起動したGoのアプリケーションをdaemonプロセスとして動かす。なお、Centos6.7
を使用している。
Supervisor: A Process Control System — Supervisor 3.3.3 documentation
Install
$ wget https://bootstrap.pypa.io/ez_setup.py -O - | python $ easy_install supervisor $ supervisord -v 3.3.3
yum
でインストールすると古いバージョン(2.x.x系)がインストールされる。古いバージョンだとsupervisorctl
コマンドでstart
やstop
を実行した時にコマンドが返ってこないので注意
Configuration
設定ファイル(/etc/supervisord.conf
)を作成する
設定は以下を参考にした。設定例は補足に記載。
SupervisorでGolangで書かれたアプリケーションのデーモン化をした話 - こんちゃんブログ
詳細は以下を参照
http://supervisord.org/configuration.html
How to use
各コマンドの詳細は以下を参照
http://supervisord.org/running.html
日本語だとこちらが詳しい
supervisorctlについて調べてみた - Masteries
supervisord
supervisorを起動するためのコマンド。以下のコマンドでsupervisorを起動する。なお、設定ファイルでautostart=true
と設定しているので、sueprvisordを起動するとGoアプリケーションも自動で立ち上がっている。
$ supervisord -c /etc/supervisord.conf
supervisorctl
supervisorでdaemonとして動かしたいアプリケーションの起動・停止するための主なコマンド
supervisorctl start <name>
supervisorctl stop <name>
supervisorctl restart <name>
- supervisorの設定ファイルを再読込せずにアプリを再起動
supervisorctl update
- supervisorの設定ファイルを再読込してアプリを再起動
supervisorctl status
$ supervisorctl start goapp goapp: started $ supervisorctl status goapp RUNNING pid 2675, uptime 0:00:02 $ supervisorctl stop goapp goapp: stopped $ supervisorctl status goapp STOPPED Dec 15 08:12 AM $ supervisorctl restart goapp goapp: stopped goapp: started $ ps aux | grep goapp root 2681 0.0 0.2 46592 5416 ? Sl 08:14 0:00 /go/bin/goapp
個人の感想
公式のドキュメントがしっかりしてて助かる
補足
設定ファイルの例
[unix_http_server] file = /var/tmp/supervisor.sock chmod = 0777 chown= root:root [supervisord] http_port=/var/tmp/supervisor.sock ; (default is to run a UNIX domain socket server) logfile=/var/log/supervisor/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=false ; (start in foreground if true;default false) minfds=1024 ; (min. avail startup file descriptors;default 1024) minprocs=200 ; (min. avail process descriptors;default 200) [supervisorctl] serverurl=unix:///var/tmp/supervisor.sock ; use a unix:// URL for a unix socket [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [program:goapp] command=/go/bin/goapp ; the program (relative uses PATH, can take args) autostart=true ; start at supervisord start (default: true) autorestart=true ; retstart at unexpected quit (default: true)
Goで始めるMiddleware その2
前回記事を書いたがもう少し詳しく書く。
なぜGoでMiddlewareを書きたいのか
前回書き忘れていたところ。
おそらくだがフレームワークを使用している場合には、フレームワークがよろしくやってくれるので自分でMiddlewareをどうこうしたいというモチベーションはあまりない気がする。しかし、あくまで個人の主観だが、標準パッケージが利用しやすいGoでは、シンプルなAPIなどであればフレームワークを使用せずに標準のnet/http
を使用して書きたいケースなどは十分あり得るはずである。そして、標準のnet/http
を使用して書く場合には自分でMiddlewareまわりを実装する必要が発生するはずである(パッケージを使用して実装するのも含め)。このため、GoにおいてはMiddlewareを書きたくなるモチベーションが生まれることは十分あり得る。
前回のおさらい
GoでシンプルにMiddlewareを実装すると以下のようになる。
Middlewares in Go: Best practices and examples – Nicolas Merouze
package main import ( "fmt" "net/http" ) // indexHandler ... func indexHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("Hello, middleware!") } // aboutHandler ... func aboutHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("This is midlleware test!!") } // middleware1 ... func middleware1(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { fmt.Println("[START] middleware1") next.ServeHTTP(w, r) fmt.Println("[END] middleware1") } } // middleware2 ... // middleware3 ... func main() { mux := http.NewServeMux() mux.HandleFunc("/", middleware1(middleware2(middleware3(indexHandler)))) mux.HandleFunc("/about", middleware1(middleware2(middleware3(aboutHandler)))) http.ListenAndServe(":8888", mux) }
このようにMiddlewareを入れ子にして実装することで、例えば複数のMiddlewareを追加したい場合も容易に実装できる。
もっといい感じにMiddlewareを書くには
GoでMiddlewareを始めることができた。Middlewareの追加もできた。
しかし、追加するMiddlewareがさらに増えるとめんどくさそうなのは容易にわかると思う。
ではこの場合はどう実装したらいいだろうか。解の一つとしてMiddlewareスタックのようなものを作るというのがある。
package main import ( "fmt" "net/http" ) // indexHandler ... // aboutHandler ... // middleware1 ... func middleware1(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { fmt.Println("[START] middleware1") next.ServeHTTP(w, r) fmt.Println("[END] middleware1") } } // middleware2 ... // middleware3 ... func main() { middlewares := newMws(middleware1, middleware2, middleware3) mux := http.NewServeMux() mux.HandleFunc("/", middlewares.then(indexHandler)) mux.HandleFunc("/about", middlewares.then(aboutHandler)) http.ListenAndServe(":8888", mux) } type middleware func(http.HandlerFunc) http.HandlerFunc type mwStack struct { middlewares []middleware } func newMws(mws ...middleware) mwStack { return mwStack{append([]middleware(nil), mws...)} } func (m mwStack) then(h http.HandlerFunc) http.HandlerFunc { for i := range m.middlewares { h = m.middlewares[len(m.middlewares)-1-i](h) } return h }
比較するとこうなる。Middlewareの入れ子を何回も書く必要がなくなる。
// 前 func main() { mux := http.NewServeMux() mux.HandleFunc("/", middleware1(middleware2(middleware3(indexHandler)))) mux.HandleFunc("/about", middleware1(middleware2(middleware3(aboutHandler)))) http.ListenAndServe(":8888", mux) } // 後 func main() { middlewares := newMws(middleware1, middleware2, middleware3) mux := http.NewServeMux() mux.HandleFunc("/", middlewares.then(indexHandler)) mux.HandleFunc("/about", middlewares.then(aboutHandler)) http.ListenAndServe(":8888", mux) }
パッケージを使うと
ではパッケージを使うとどうだろうか。ここではAliceとnegroniを紹介する。
Alice
Aliceを使用すると前述したMiddlewareスタックのようなものを容易に実装できる(というか元ネタ)。以下、Aliceの説明を引用する。
Alice provides a convenient way to chain your HTTP middleware functions and the app handler.
In short, it transforms
Middleware1(Middleware2(Middleware3(App)))
to
alice.New(Middleware1, Middleware2, Middleware3).Then(App)
コードを見ればわかるが考え方的には前述したMiddlewareスタックのようなものをalice.New(Middleware1, Middleware2, Middleware3)
で作ってから.Then(App)
でハンドラを返すようになっている。Aliceを使用すると実装は以下のようになる。
package main import ( "fmt" "net/http" "github.com/justinas/alice" ) // indexHandler ... // aboutHandler ... // middleware1 ... func middleware1(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Println("[START] middleware1") next.ServeHTTP(w, r) fmt.Println("[END] middleware1") }) } // middleware2 ... // middleware3 ... func main() { mux := http.NewServeMux() mux.HandleFunc("/", indexHandler) mux.HandleFunc("/about", aboutHandler) chain := alice.New(middleware1, middleware2, middleware3) http.ListenAndServe(":8888", chain.Then(mux)) }
negroni
negroniも同様にMiddlewareスタックのようなものを扱うが、これまで紹介したものと違い実装する側がそのスタックを意識しなくてよい。negroni独自にHandler
インターフェースを用意しており、これに沿って実装すれば容易にMiddlewareの追加もできる。また、デフォルトでロガーやリカバリー処理(negroni.Classic()
)を用意してくれており、比較的リッチなパッケージとなっている。
https://github.com/urfave/negroni#negroniclassic
negroniを使用すると実装は以下のようになる。
package main import ( "fmt" "net/http" "github.com/urfave/negroni" ) // indexHandler ... // aboutHandler ... // middleware1 ... func middleware1(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { fmt.Println("[START] middleware1") next(w, r) fmt.Println("[END] middleware1") } // middleware2 ... // middleware3 ... func main() { mux := http.NewServeMux() mux.HandleFunc("/", indexHandler) mux.HandleFunc("/about", aboutHandler) n := negroni.New() n.Use(negroni.HandlerFunc(middleware1)) n.Use(negroni.HandlerFunc(middleware2)) n.Use(negroni.HandlerFunc(middleware3)) n.UseHandler(mux) http.ListenAndServe(":8888", n) }
感想
Goを書き始めてから初めてMiddlewareを意識した。最初はよくわからなかったが実際に書いてみるとそんなに怖くなかった。とはいえGoを書き始めて間もないので他によさげなパッケージがあれば教えてほしいです。
書いたコードはここに置きました。
Goの標準logでやってるintをasciiにする変換処理を見てみる
Goのlogパッケージでやってるintをasciiにする変換処理が何やってるか最初わけわかんなかったけど、多少理解できたからメモしておく。対象のコードは以下の通り。標準で年月日をログの先頭に付与できる機能(2009/01/23 01:23:23 log-message
みたいな感じ)があってそこで使用されている。
src/log/log.go - The Go Programming Language
// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding. func itoa(buf *[]byte, i int, wid int) { // Assemble decimal in reverse order. var b [20]byte bp := len(b) - 1 for i >= 10 || wid > 1 { wid-- q := i / 10 b[bp] = byte('0' + i - q*10) bp-- i = q } // i < 10 b[bp] = byte('0' + i) *buf = append(*buf, b[bp:]...) }
例えば、
itoa(buf, 2017, 4)
とすると、buf
には'2''0''1''7'
が設定される。つまり、2017
とログに出力される。
まず、単純にintをasciiにする処理を見てみる。上記のコードではb[bp] = byte('0' + i - q*10)
やb[bp] = byte('0' + i)
に当たるところ。
そもそもasciiでは10進数の数字と文字の数字は以下のように対応している。
10進数 : 文字 48 : 0 49 : 1 50 : 2 51 : 3 52 : 4 53 : 5 54 : 6 55 : 7 56 : 8 57 : 9
このため、例えばintの7をasciiの7にしたい場合にはintの55を持つ必要がある。このintからasciiへの変換処理をどのように実装するかというと、byte('0' + i)
に当たる。つまり、byte('0' + 7) = byte(48 + 7) = byte(55) = '7'
という具合である。'0' = 48
を基準にしてここにintを足すことでintからasciiへの変換処理を実現している。
では、b[bp] = byte('0' + i - q*10)
の処理はどうだろうか。なお、wid
に関してはここでは説明しない。例えばi = 2017
とすると1回目のループで各変数は以下のようになる。
// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding. func itoa(buf *[]byte, i int, wid int) { // Assemble decimal in reverse order. var b [20]byte bp := len(b) - 1 // bp = 19 for i >= 10 || wid > 1 { wid-- q := i / 10 // q = 2017 / 10 = 201 b[bp] = byte('0' + i - q*10) // b[19] = byte('0' + 2017 - 201*10) = byte('0' + 2017 - 2010) = byte(48 + 7) = byte(55) = '7' bp-- // bp = 18 i = q // i = 201 } // i < 10 b[bp] = byte('0' + i) *buf = append(*buf, b[bp:]...) }
これを見るとわかるように2017の7(int)を'7'(ascii)と変換していることがわかる。また、ループを繰り返すと一の位から順にintからasciiに変換していることもわかる。
上記のようにしてintからasciiへの変換を実現している。
まとめ
標準パッケージは全て「イカした」実装していて自分じゃ思いつかないようなやり方ばっかりだろうから読んでもわからなさそう、とか勝手に思い込んでた。だけど、読んでみるとプレーンな実装箇所は案外泥臭いやり方(上記のq := i / 10
のところとか!)だし、理解できるところも多くて、今まで自分が勝手に持ってた心理的障壁みたいなものがちょっと取り除かれた気がする。
Goで始めるMiddleware
そもそもMiddlewareとは何かと言うと、私の認識ではアプリケーションの処理の前後で何らかの処理を行ったりするもの。例えば、ログを追加したりリクエストのAPIキーを認証したりする。以下の図がわかりやすい。
Laravel 5.0 - Middleware (Filter-style) | MattStauffer.com より引用
とはいえ、上記だけでは具体的にどのように実装したらイメージがつかなかったんだけど、Goの公式のwikiでいろいろ紹介していてわかりやすかったのでこれを元に簡単なサンプルを実装した。
LearnServerProgramming · golang/go Wiki · GitHub
Middlewares in Go: Best practices and examples – Nicolas Merouze
package main import ( "fmt" "net/http" "time" ) // indexHandler ... func indexHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("Hello, middleware!") } // aboutHandler ... func aboutHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("This is midlleware test!!") } // loggingHandler ... func loggingHandler(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { t1 := time.Now() next.ServeHTTP(w, r) t2 := time.Now() fmt.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t2.Sub(t1)) } } func main() { http.HandleFunc("/", loggingHandler(indexHandler)) http.HandleFunc("/about", loggingHandler(aboutHandler)) http.ListenAndServe(":8888", nil) }
ポイントは、
http.HandlerFunc
を返すラッパーを作ること- ラッパーの中で引数の
http.HandlerFunc
を呼び出すこと
だと個人的に思った。前述の図との対応としては、処理のイメージとしてはこんな感じだろうか。Middlewareで前処理を行って、アプリケーションの処理( next.ServeHTTP(w, r)
)を行って、Middlewareで後処理を行う。
// sampleHandler ... func sampleHandler(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // 前処理 next.ServeHTTP(w, r) // 後処理 } }
まとめ
Middlewareは怖くない
CentOSでユーザ追加
DockerでGoの環境構築する際にCentOSでユーザ追加したのでメモ。
$ useradd -m -d /home/docker -s /bin/bash docker $ echo "docker:docker" | chpasswd $ echo "docker ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
useradd
:新規ユーザの追加chpasswd
でパスワードの更新
それぞれ詳細はman
コマンドで確認できる。
追加されたユーザは/etc/passwd
ファイルで確認できる。なお、passwd
ファイルの詳細はman 5 passwd
で確認できる。
/etc/sudoers
ファイルでユーザのsudo権限を管理している。dockerで環境構築した際には上記のコマンドで設定できたが、通常ではrootでさえ編集不可のファイルとなっている。こちらもman
コマンドで詳細を確認できる。
感想
普段の作業では設定しないところだったので忘れないようにメモした。そしてman
コマンドってこんなに便利なんだってことがわかった。
Real World HTTP を読んだ
Real World HTTPを読んだ。前に読んだやつ。
HTTPについてそれが作成されてきた歴史と併せて説明されておりわかりやすかった。RFCのリファレンスにもなっており、これを通して初めてRFCの内容を読んだ。ハイパフォーマンスブラウザネットワーキングではパフォーマンスの観点からの説明だけであったのに対して、Real World HTTPでは一般的に使用される機能に関して説明してあり、HTTPの入門書としてもいいんじゃないかと思った。
- HTTP HEADERの説明が一通りあって、今までスルーしてた部分をフォローできた。
- Cache-Controlについてもう少し詳しく調べた
リクエスト時の Cache-Control、max-age=0 と no-cache の違い | tech - 氾濫原
RFC 7234 - Hypertext Transfer Protocol (HTTP/1.1): Caching
ちなみにGOによる実装の部分はスルーしたが、最近になってGoを書くようになったので改めて読み直したい。