간단한 Web Framework 구현하기 (6) - Abstraction
13 Mar 2018 | Go추상화
라우터를 직접 생성하는 것보다 생성자 함수로 감싸는 것이 좀 더 안전한 사용법이며, 미들웨어도 대부분의 핸들러에 적용이 되어야 하기 때문에 미들웨어 체인 형태로 사용하는 것이 더 간편할 수 있습니다.
Server라는 패키지를 만들어서 그 안에서 라우터 생성 및 미들웨어 체인을 사용하도록 한 코드입니다.
server.go
package main
import "net/http"
type Server struct {
*router
middlewares []Middleware
startHandler HandlerFunc
}
func NewServer() *Server {
r := &router{make(map[string]map[string]HandlerFunc)}
s := &Server{router: r}
s.middlewares = []Middleware{
logHandler,
recoverHandler,
staticHandler,
parseFormHandler,
parseJsonBodyHandler,
}
return s
}
func (s *Server) Run(addr string) {
s.startHandler = s.router.handler()
for i := len(s.middlewares) - 1; i >= 0; i-- {
s.startHandler = s.middlewares[i](s.startHandler)
}
if err := http.ListenAndServe(addr, s); err != nil {
panic(err)
}
}
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := &Context{
Params: make(map[string]interface{}),
ResponseWriter: w,
Request: req,
}
for k, v := range req.URL.Query() {
c.Params[k] = v[0]
}
s.startHandler(c)
}
router.go
package main
import (
"net/http"
"strings"
)
type router struct {
handlers map[string]map[string]HandlerFunc
}
func (r *router) handler() HandlerFunc {
return func(c *Context) {
for pattern, handler := range r.handlers[c.Request.Method] {
if ok, params := match(pattern, c.Request.URL.Path); ok {
for k, v := range params {
c.Params[k] = v
}
handler(c)
return
}
}
http.NotFound(c.ResponseWriter, c.Request)
}
}
func (r *router) HandleFunc(method, pattern string, h HandlerFunc) {
m, ok := r.handlers[method]
if !ok {
m = make(map[string]HandlerFunc)
r.handlers[method] = m
}
m[pattern] = h
}
func match(pattern, path string) (bool, map[string]string) {
if pattern == path {
return true, nil
}
patterns := strings.Split(pattern, "/")
paths := strings.Split(path, "/")
if len(patterns) != len(paths) {
return false, nil
}
params := make(map[string]string)
for i := 0; i < len(patterns); i++ {
switch {
case patterns[i] == paths[i]:
case len(patterns[i]) > 0 && patterns[i][0] == ':':
params[patterns[i][1:]] = paths[i]
default:
return false, nil
}
}
return true, params
}
main.go
package main
import (
"fmt"
)
func main() {
fmt.Println("Simple Web Framework")
s := NewServer()
s.HandleFunc("GET", "/", func(c *Context) {
fmt.Fprintln(c.ResponseWriter, "This is an index page.")
})
s.HandleFunc("GET", "/about", func(c *Context) {
fmt.Fprintln(c.ResponseWriter, "This is an about page.")
})
s.HandleFunc("GET", "/users/:id", func(c *Context) {
userId := c.Params["id"]
fmt.Fprintln(c.ResponseWriter, "Retrieve user: ", userId)
if userId == "-1" {
panic("id is not valid..")
}
})
s.HandleFunc("POST", "/users", func(c *Context) {
fmt.Fprintln(c.ResponseWriter, "Create user")
})
s.Run(":8080")
}