간단한 Web Framework 구현하기 (4) - Middleware
13 Mar 2018 | GoMiddleware
로그를 남기거나 에러 처리(500 Internal Server Error 등)를 하는 미들웨어는 다음과 같이 구현할 수 있습니다.
middleware.go
package main import ( "time" "log" "net/http" ) type Middleware func(next HandlerFunc) HandlerFunc func logHandler(next HandlerFunc) HandlerFunc { return func(c *Context) { t := time.Now() next(c) log.Printf("[%s] %q %v\n", c.Request.Method, c.Request.URL.String(), time.Now().Sub(t)) } } func recoverHandler(next HandlerFunc) HandlerFunc { return func(c *Context) { defer func() { if err := recover(); err != nil { log.Printf("panic: %+v", err) http.Error(c.ReponseWriter, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } }() next(c) } }
이를 호출하는 부분을 다음과 같이 수정하면, 미들웨어를 적용할 수 있습니다.
main.go
... r.HandleFunc("GET", "/users/:id", logHandler(recoverHandler(func(c *Context) { userId := c.Params["id"] fmt.Fprintln(c.ReponseWriter, "Retrieve user: ", userId) if userId == "-1" { panic("id is not valid..") } }))) ...
json Parsing 미들웨어
그 외에도 Post 메시지 내용이나 json 파싱을 하는 미들웨어 코드는 다음과 같습니다.
func parseFormHandler(next HandlerFunc) HandlerFunc { return func(c *Context) { fmt.Println("HandlerFunc is called.") c.Request.ParseForm() fmt.Println(c.Request.PostForm) for k, v := range c.Request.PostForm { if len(v) > 0 { c.Params[k] = v[0] } } next(c) } } func parseJsonBodyHandler(next HandlerFunc) HandlerFunc { return func(c *Context) { fmt.Println("parseJsonBodyHandler is called.") var m map[string]interface{} if json.NewDecoder(c.Request.Body).Decode(&m); len(m) > 0 { for k, v := range m { c.Params[k] = v } } next(c) } }
정적 파일 처리를 위한 미들웨어
func staticHandler(next HandlerFunc) HandlerFunc { var ( dir = http.Dir(".") indexFile = "index.html" ) return func(c *Context) { if c.Request.Method != "GET" && c.Request.Method != "HEAD" { next(c) return } file := c.Request.URL.Path f, err := dir.Open(file) if err != nil { next(c) return } defer f.Close() fi, err := f.Stat() if err != nil { next(c) return } if fi.IsDir() { if !strings.HasSuffix(c.Request.URL.Path, "/") { http.Redirect(c.ResponseWriter, c.Request, c.Request.URL.Path+"/", http.StatusFound) return } file = path.Join(file, indexFile) f, err = dir.Open(file) if err != nil { next(c) return } defer f.Close() fi, err = f.Stat() if err != nil || fi.IsDir() { next(c) return } } http.ServeContent(c.ResponseWriter, c.Request, file, fi.ModTime(), f) } }