Go로 만드는 웹-6 Decorator Handler

솔다·2023년 2월 1일
0

지난 포스팅때 예고했던 것처럼 오늘은 Handler를 구현해볼 예정이다.

Decorator Handler

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/hoondal6970/Go_server_prac/web6/myapp"
)

func logger(w http.ResponseWriter, r *http.Request, h http.Handler) {
	start := time.Now()
	log.Print("[LOGGER1] Started")
	h.ServeHTTP(w, r)
	log.Println("[LOGGER1] Completed time: ", time.Since(start).Millisecond)
}

func NewHandler() http.Handler {
	mux := myapp.NewHandler()
	return mux
}

func main() {
	mux := NewHandler()
    //Decorator로 감싸준다.
    mux = decoHandler.NewDecoHandler(mux, logger)

	http.ListenAndServe("3000", mux)
}

우선 main.go 부터 만들고 시작한다. 앞선 포스팅에서 했던 것과 같이, NewHandler()를 만들어주고, 서버 세팅부터 한다.

그리고, Logger라는 이름의 log를 찍어주는 역할의 Decorator를 만들어준다.

logger()를 뜯어보면, log를 찍어주는데, Handler()가 실행되기 전의 시간과, 그때의 시간부터 걸린 시간을 time.Since()로 찍어주는 decorator이다.

당연히 test code도 한번 작성해보자.

func TestDecoHandler(t *testing.T) {
	assert := assert.New(t)

	ts := httptest.NewServer(NewHandler())
	defer ts.Close()

	buf := &bytes.Buffer{}
	log.SetOutput(buf)

	resp, err := http.Get(ts.URL)
	assert.NoError(err)
	assert.Equal(http.StatusOK, resp.StatusCode)

	r := bufio.NewReader(buf)
	line, _, err := r.ReadLine()
    assert.NoError(err)
	assert.Contains(string(line), "[LOGGER] Started")
}

log가 찍히는 것을 확인하는 테스트이다.
log.SetOutput()은 log가 화면에 찍히는 것의 위치를 인자로 받는 곳으로 바꿔주는 함수인데 여기에 buffer를 하나 넣어서 buffer를 확인하는 방식으로 확인한다.

buffer에서 log가 어떤방식으로 되어있나? main.go를 보면 한줄씩 log를 작성한다.

지금 저 buffer는 binary buffer이기 때문에, bufio.NewReader() bufio 패키지의 NewReader()를 사용해서 읽을 수 있도록 바꿔준다. 이후 Readline()을 사용하면 한번에 한 줄씩 읽는다.

기본적인 세팅이 끝났으니, Decorator를 설정해주자.

Decorator 세팅

package decoHandler

import "net/http"

type DecoratorFunc func(http.ResponseWriter, *http.Request, http.Handler)

type DecoHandler struct {
	fn DecoratorFunc
	h  http.Handler
}

func (self *DecoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	self.fn(w, r, self.h)
}

func NewDecoHandler(h http.Handler, fn DecoratorFunc) http.Handler {
	return &DecoHandler{
		fn: fn,
		h:  h,
	}
}

Decorator를 위해서 간단한 decoHandler패키지를 작성한 코드이다.

DecoratorFunc type을 지정해주고, ServeHTTP() 인터페이스가 있다. 이 함수는 추후에 main.go에서 Logger()함수에서 handler()를 호출해 줄 것이다.

말로만 적혀있어서 헷갈릴 수 있는데, 실행되는 순서대로 따라가면 이해에 도움이 된다.

main.go 파일에서, NewHandler()로 생성된 mux를 Decorator로 감싸준다.NewDecoHandler()로 리턴된 새로운 mux로 서버가 실행된다.

테스트 코드로 GET 요청을 보내면, 순서대로 Log를 먼저찍게되고 내부에서 h.ServeHTTP()로 함수가 실행되면서 실제 request에 대한 response를 보내게된다. 그리고 다시 log로 실행에 걸린 시간이 문구와 함께 찍히게 된다.

아래와 같이 main.go에 한번더 감싸주게 되면 쉽게 Decorator를 추가할 수 있다

func logger(w http.ResponseWriter, r *http.Request, h http.Handler) {
	start := time.Now()
	log.Print("[LOGGER1] Started")
	h.ServeHTTP(w, r)
	log.Println("[LOGGER1] Completed time: ", time.Since(start).Milliseconds())
}

func logger2(w http.ResponseWriter, r *http.Request, h http.Handler) {
	start := time.Now()
	log.Print("[LOGGER2] Started")
	h.ServeHTTP(w, r)
	log.Println("[LOGGER2] Completed time: ", time.Since(start).Milliseconds())
}

func NewHandler() http.Handler {
	h := myapp.NewHandler()
	h = decoHandler.NewDecoHandler(h, logger)
	h = decoHandler.NewDecoHandler(h, logger2)
	return h
}

위 코드는 main.go 파일에서 일부분만 추가했다. logger2함수를 추가했고, 이를 NewDecoHandler()로 한번 더 감싸줬다. 이렇게 작성하면

2023/02/01 11:18:51 [LOGGER2] Started
2023/02/01 11:18:51 [LOGGER1] Started
2023/02/01 11:18:51 [LOGGER1] Completed time:  0
2023/02/01 11:18:51 [LOGGER2] Completed time:  195

이런 로그를 얻게된다. Logger2()가 먼저 실행되면서 Logger1()을 내부에서 호출하는 식으로 실행되며 그대로 log가 찍힌 것을 볼 수 있다.

다음 포스팅에서는 Web Template에 대해서 작성할 예정이다. 더 자세한 강의를 보고 싶으면 유툽에 올라와있는 Tucker Programming의 강의를 보기를 추천한다.

(출처:Tucker의 Go로 만드는 웹)

0개의 댓글