간단한 Web Framework 구현하기 (4) - Middleware

|

Middleware

로그를 남기거나 에러 처리(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)
	}
}