Golangのmiddlewear実装パターンを学ぶ

Golangでwebアプリケーションなどを開発するに当たり、
リクエストやレスポンスをインターセプトし適宜利用するmiddlewearの実装パターンを
読んでみたときのメモ。

今回はHTTPサーバーおよびHTTPクライアントを読んでみた。
結果として、標準パッケージのみで比較的容易に実装できる。
これはgolangの柔軟な言語仕様なんだろう。
golangらしいコーディングの勉強にもなりました。

http server callstack

およそ一般的なechoサーバーの実装は以下のような感じと思われる

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"net/http"
)

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

func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":3000", nil)
}

callstackを見ていくと

1
2
3
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}

patternに適合したhandlerを登録する。DefaultServeMuxで初期設定で提供されている。
ちなみにServeMuxはhttpマルチプレクサになる。

また、コードコメントに以下のようにある

1
2
3
4
5
6
Longer patterns take precedence over shorter ones, so that
if there are handlers registered for both "/images/"
and "/images/thumbnails/", the latter handler will be
called for paths beginning "/images/thumbnails/" and the
former will receive requests for any other paths in the
"/images/" subtree.

パスのパターンマッチはより長いパターンのほうが短いパターンより優先されるとのこと。
さらにhandler optionは

1
handler func(ResponseWriter, *Request)

ここからわかるようにhandlerはrequest writerとresponseの参照をオプションとする関数である。
これを満たせばよい。

1
2
3
4
5
6
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}

HandleFuncはServeMuxのHandleをcallすることになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}

func (mux *ServeMux) Handle(pattern string, handler Handler) {
...

e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}

...
}

pathにマッチしているhandlerを登録している。

次はこのhandlerを呼び出すところ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// net/http/server.go

func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}

...

func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

...

func (srv *Server) Serve(l net.Listener) error {
...

for {
...

c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}

...

func (c *conn) serve(ctx context.Context) {
...

// HTTP cannot have multiple simultaneous active requests.[*]
// Until the server replies to this request, it can't read another,
// so we might as well run the handler in this goroutine.
// [*] Not strictly true: HTTP pipelining. We could let them all process
// in parallel even if their responses need to be serialized.
// But we're not going to implement HTTP pipelining because it
// was never deployed in the wild and the answer is HTTP/2.
serverHandler{c.server}.ServeHTTP(w, w.req)

...

}

...

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}

最終的にhandlerはServeHTTPをcallする形になる。

そしてこのhanderは以下のinterfaceを持っている。

1
2
3
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

これから作成するmiddlewearはこのインターフェイスを満たす関数である必要がある。

1
Except for reading the body, handlers should not modify the provided Request.

middlewear自体の内部実装の注意点としては、
公式のコメントにある通りでRequestBodyをあくまでReadするだけで変更してはならない。

またServerにはHandleHandleFuncが実装されており、HandleFuncをつかうことでハンドラ関数を登録できることわかる。

1
2
3
4
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}

このHandleFuncのServerHTTPは以下のような実装である

1
2
3
4
5
6
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

HandlerFuncをcallする関数になっている。

middlewearを実装

ここまで見てきた通りmiddlewearはhandlerで実装されるように設計していくことで、あとは既存の機構で適宜処理される。
またmiddlewerはアプリケーションの処理の前後にmiddlewearの処理挟むため、イメージ的に

[request] –> [middlewear] –> [another middlewear] –>
… –> [final] –> [middlewear] –> [another middlewear] –> … –> [response]

という動作が期待される

具体的には以下のような感じになってほしいものである。

1
http.Handle("/", middleware1(middleware2(... (finalHandler) ...))

引数として受けた次のmiddlewareを呼び出す前後に何らかの操作を実行し。
新しいmiddleware1を返す関数でmiddlewearを実装する必要がある。

各middleware関数は実装としては以下の形であればよい

1
2
3
4
5
func middleware(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
})
}

HandlerFuncをオプションにとり、HandlerFuncを返す関数である。

http.HandlerFuncはinterfaceからServeHTTPを実装しているので最終的にそれをcallする。
あとはmiddlewearを複数登録できるようにラッパー関数を作成していく。

ということで実装してみる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// add type for middlewear
type middleware func(http.HandlerFunc) http.HandlerFunc

// add middlewear function
func useMiddlewears(middlewares ...middleware) middleware {
// final handler register function
return func(final http.HandlerFunc) http.HandlerFunc {
// Pass the previous function as an argument
return func(w http.ResponseWriter, req *http.Request) {
fn := final
for i := len(middlewares) - 1; i >= 0; i-- {
fn = middlewares[i](fn)
}
fn(w, req)
}
}
}

上記のようにmniddlewearというタイプを作成し、middlewearを登録するラッパー関数を作成する。
今回は最終的にリクエストを処理する関数を別途登録するデザインとなっている。
またリスト内のfinal handlerから順番に登録した順番と逆順で直前の関数に渡すようにすることで、

1
http.Handle("/", middleware1(middleware2(... (finalHandler) ...))

このパターンを実現できる。
最終的に以下のようになった。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import (
"log"
"net/http"
)

type middleware func(http.HandlerFunc) http.HandlerFunc

func middlewareA(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Executing middlewareA before handler")
next.ServeHTTP(w, r)
log.Println("Executing middlewareA after handler")
})
}

func middlewareB(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Executing middlewareB before handler")
next.ServeHTTP(w, r)
log.Println("Executing middlewareB after handler")
})
}

func finalHandler(w http.ResponseWriter, r *http.Request) {
log.Println("Executing finalHandler")
w.Write([]byte("Done"))
}

func useMiddlewears(middlewares ...middleware) middleware {
return func(final http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
fn := final
for i := len(middlewares) - 1; i >= 0; i-- {
fn = middlewares[i](fn)
}
fn(w, req)
}
}
}

func main() {
middlewears := useMiddlewears(middlewareA, middlewareB)
http.HandleFunc("/", middlewears(finalHandler))
http.ListenAndServe(":3000", middlewears(finalHandler))
}
1
2
3
4
5
6
7
8
9
$ go run main.go
// $ curl http://localhost:3000/
DONE

2019/05/02 19:03:56 Executing middlewareA before handler
2019/05/02 19:03:56 Executing middlewareB before handler
2019/05/02 19:03:56 Executing finalHandler
2019/05/02 19:03:56 Executing middlewareB after handler
2019/05/02 19:03:56 Executing middlewareA after handler

ということで期待通りの実装ができた。

まとめ

1
2
3
4
5
6
7
8
9
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
...
type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

http.Handlerが以下のinterfaceに依存しており、
ServeHTTPの実装がhandlerを呼ぶ形になっている。
この実装のため非常に効果的にmiddlewearを作成することができる。

このあたりがgolangは柔軟に設計されていて,interfaceにのみ依存したkたちで
実際の実装をしていく感じを体験できるものだと思う。
次はhttp.Clientでも同じように見ていければ。

Comments