간단한 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)
}
}