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

|

Context

Context는 사용자로부터 URL 패턴을 받았을 때, 해당 매개변수 및 Request, Repose 등을 저장할 수 있는 구조체입니다.

여기서는 다음과 같이 정의했습니다.

context.go

package main

import "net/http"

type Context struct {
	Params map[string]interface{}

	ResponseWriter http.ResponseWriter
	Request        *http.Request
}

type HandlerFunc func(*Context)


router.go

위에서 만든 Context 구조체를 이용해서 기존의 http.ResponseWriterhttp.Request를 저장할 수 있습니다. 또한 이 구조체를 매개변수로 하는 HandlerFunc라는 인터페이스를 정의했기 때문에 기존의 http.HandlerFunc() 함수 호출 부분을 수정해줍니다.

package main

import (
	"net/http"
	"strings"
)

type router struct {
	handlers map[string]map[string]HandlerFunc
}

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 (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	for pattern, handler := range r.handlers[req.Method] {
		if ok, params := match(pattern, req.URL.Path); ok {
			// Create Context

			c := Context{
				Params:         make(map[string]interface{}),
				ResponseWriter: w,
				Request:        req,
			}

			for k, v := range params {
				c.Params[k] = v
			}

			handler(&c)
			return
		}
	}

	http.NotFound(w, req)
}

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"
	"net/http"
)

func main() {
	fmt.Println("Simple Web Framework")

	r := &router{make(map[string]map[string]HandlerFunc)}

	r.HandleFunc("GET", "/", func(c *Context) {
		fmt.Fprintln(c.ResponseWriter, "This is an index page.")
	})

	r.HandleFunc("GET", "/about", func(c *Context) {
		fmt.Fprintln(c.ResponseWriter, "This is an about page.")
	})

	r.HandleFunc("GET", "/users/:id", func(c *Context) {
		fmt.Fprintln(c.ResponseWriter, "Retrieve user: ", c.Params["id"])
	})

	r.HandleFunc("POST", "/users", func(c *Context) {
		fmt.Fprintln(c.ResponseWriter, "Create user")
	})

	http.ListenAndServe(":8080", r)
}

간단한 Web Framework 구현하기 (3) - Router

|

Router 추가

사용자로부터 요청받은 Request Method(GET 또는 POST 등) 및 URL에 따라 각각을 처리할 수 있는 모듈인 Router를 구현해봅니다.

main.go

package main

import (
	"fmt"
	"net/http"
)

func main() {
	fmt.Println("Simple Web Framework")

	r := &router{make(map[string]map[string]http.HandlerFunc)}

	r.HandleFunc("GET", "/", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(w, "This is an index page.")
	})

	r.HandleFunc("GET", "/about", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(w, "This is an about page.")
	})

	http.ListenAndServe(":8080", r)
}


router.go

package main

import (
	"net/http"
)

type router struct {
	handlers map[string]map[string]http.HandlerFunc
}

func (r *router) HandleFunc(method, pattern string, handler http.HandlerFunc) {
	m, ok := r.handlers[method]

	if !ok {
		m = make(map[string]http.HandlerFunc)
		r.handlers[method] = m
	}
	m[pattern] = handler
}

func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if m, ok := r.handlers[req.Method]; ok {
		if handler, ok := m[req.URL.Path]; ok {
			handler(w, req)
			return
		}
	}

	http.NotFound(w, req)
}


router에 정규식 적용

main.go

package main

import (
	"fmt"
	"net/http"
)

func main() {
	fmt.Println("Simple Web Framework")

	r := &router{make(map[string]map[string]http.HandlerFunc)}

	r.HandleFunc("GET", "/", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(w, "This is an index page.")
	})

	r.HandleFunc("GET", "/about", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(w, "This is an about page.")
	})

	r.HandleFunc("GET", "/users/:id", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(w, "Retrieve user")
	})

	r.HandleFunc("POST", "/users", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(w, "Create user")
	})

	http.ListenAndServe(":8080", r)
}


router.go

package main

import (
	"net/http"
	"strings"
)

type router struct {
	handlers map[string]map[string]http.HandlerFunc
}

func (r *router) HandleFunc(method, pattern string, h http.HandlerFunc) {
	m, ok := r.handlers[method]

	if !ok {
		m = make(map[string]http.HandlerFunc)
		r.handlers[method] = m
	}
	m[pattern] = h
}

func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	for pattern, handler := range r.handlers[req.Method] {
		if ok, _ := match(pattern, req.URL.Path); ok {
			handler(w, req)
			return
		}
	}

	http.NotFound(w, req)
}

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
}

간단한 Web Framework 구현하기 (2) - 간단한 Web App

|

간단한 Web App

먼저 단순히 사용자의 Request와 Response를 처리하는 프로그램을 작성합니다.

main.go

package main

import (
	"fmt"
	"net/http"
)

func main() {
	fmt.Println("Simple Web Framework")

	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(w, "This is an index page.")
	})

	http.HandleFunc("/about", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(w, "This is an about page.")
	})

	http.ListenAndServe(":8080", nil)
}

간단한 Web Framework 구현하기 (1) - 구조

|

Go언어 기반 간단한 Web Framework 구현

사용자로부터 요청(Request)이 오면 응답(Response)를 할 수 있는 간단한 Web Framework 예제코드입니다.

참고로 한 책은 Go언어 웹프로그래밍 철저입문입니다.


Web Framework 요구사항

Web Framework는 다음과 같은 기능을 제공해야 합니다.

  • URI 패턴 맵핑 기능
  • 로그 처리
  • 에러 처리
  • 정적 파일 처리
  • 사용자 인증 및 권한 관리
  • 보안 처리
  • 세션 상태 관리
  • 데이터베이스 관리
  • 웹 요청 및 응답 추상화


Web Framework 구조도

Image

각각의 요소들은 go 언어를 이용해서 하나씩 구현해봅니다.

Shift + 숫자 키패드 동작을 Windows 처럼 동작하도록 설정

|

Shift + 숫자 키패드 동작을 Windows 처럼 동작하도록 설정

Ubuntu 버전마다 설정 방법의 차이가 있을 수 있습니다.

Ubuntu 16.04 LTS 에서는 gnome-tweak-tool 툴을 이용하는 것이 간편합니다.

  • gnome-tweak-tool 설치(sudo apt-get install gnome-tweak-tool)
  • gnome-tweak-tool 실행
  • Typing 항목 선택
  • Miscellaneous compatibility options 선택
  • Shift with numeric keypad keys works as in MS Windows 선택


만약 Ubuntu Mint 같은 버전을 쓸 경우에는 기본적으로 Keyboard Setting에 위 항목이 존재하는 경우가 있습니다.

  • System Settings에서 Keyboard 선택
  • Options 선택
  • Miscellaneous keyboard options 선택
  • Shift with numeric keypad keys works as in MS Windows 선택