이 페이지에서는 핸들러와 멀티플렉서 개념 및 HandleFunc 함수에 대해 다룬다.
001에서는 핸들러를 사용해 / 경로에 대한 Index Page를 생성하는 것을 알아보았다. 하지만 아직 핸들러와 멀티플렉서가 무엇인지 정확이 감이 잡히지 않았을 것이라 생각한다. 이번 포스팅에서는 핸들러와 멀티플렉서에 대해 자세히 다뤄보고 좀 더 편하게 핸들러를 정의할 수 있는 HandleFunc 함수를 테스트해볼 것이다.
1. 핸들러
핸들러는 인터페이스이다. 핸들러의 정의는 다음과 같다.
type Handler interface{
ServeHTTP(ResponseWriter, *Request)
}
위의 코드를 보면 ServeHTTP(ResponseWriter, *Request) 메서드를 구현한 모든 구조체는 핸들러 인터페이스를 구현한 것이라고 볼 수 있다. ServeHTTP메서드는 서버 실행 시 요청에 대한 응답을 작성한다. 구현 예시는 다음과 같다.
type indexHandler struct{}
func (i *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Index Page")
}
위의 ServeHTTP 메서드는 http.ResponseWriter에 Index Page 문자열을 출력한다.
2. 멀티플렉서
웹 서비스는 다양한 요청에 대한 응답이 필요하다. 이 요청들은 경로와 메서드(GET, PUT, PATCH, DELETE 등..)에 따라 나누어지게 되는데, 여러 경로로 들어오는 요청에 적절한 핸들러를 매칭 시키기 위해 멀티플렉서를 사용한다.
멀티플렉서는 net/http 패키지의 ServeMux 타입이다. 그리고 기본 멀티플렉서는 DefaultServeMux이다. net/http 패키지의 코드를 살펴보면 ServeMux 구조체를 미리 선언하여 DefaultServeMux에 할당하는 것을 볼 수 있다.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
그리고 http.ListenAndServe함수의 두 번째 매개변수를 nil로 할당할 경우 자동으로 DefaultServeMux가 사용된다.
http.ListenAndServe(":8080", nil) //DefaultServeMux 사용
디폴트멀티플렉서에 핸들러를 연결하는 방법은 대표적으로 두 가지 함수가 있다.
func Handle(pattern string, handler Handler)
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
001에서는 http.Handle 함수를 통해 핸들러를 등록했다.
http.Handle("/", &index)
실제 http.Handle 함수의 정의는 매우 간단하다. 아래의 코드를 보면 DefaultServeMux.Handle 함수에 파라미터를 그대로 전달하고 호출한다. 이를 통해 http.Handle는 DefaultServeMux에 핸들러를 등록하는 함수라고 정의 내릴 수 있다.
func Handle(pattern string, handler Handler) {
DefaultServeMux.Handle(pattern, handler)
}
그러면 DefaultServeMux.Handle 메서드는 핸들러를 어떻게 등록할까? 가장 아래 영역이기 때문에 코드의 일부만 추출했다.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
...
e := muxEntry{h: handler, pattern: pattern} //엔트리 정의
mux.m[pattern] = e //패턴에 일치하는 엔트리 등록
...
}
맨 앞에서 언급한 것처럼 DefaultServeMux는 ServeMux를 구현한 구조체이다. 따라서 위의 Handle 메서드를 DefaultServeMux.Handle로 호출할 수 있다. 정의를 자세히 살펴보면 mux.m[pattern] = e 부분에서 자신의 m 변수에 맵 형태로 패턴과 엔트리를 등록하는 것을 알 수 있다.
3. http.HandleFunc
다시 001에서 구현한 웹 코드를 살펴보자.
package main
import (
"fmt"
"net/http"
)
type indexHandler struct{}
func (i *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Index Page")
}
func main() {
index := indexHandler{}
http.Handle("/", &index)
http.ListenAndServe(":8080", nil)
}
해당 방법으로 3개의 핸들러를 등록하려면 어떻게 해야할까? 타입을 3개 선언하고, 타입마다 ServeHTTP 메서드를 선언하고, main 함수에서 타입을 할당한 후 http.Handle 함수를 통해 핸들러로 등록한다. 핸들러가 늘어날수록 코드가 복잡해지고 구현이 번거롭기 때문에, http.HandleFunc 함수를 사용해 더 간단하게 핸들러를 등록할 수 있다.
코드: https://github.com/daintree-henry/studygo/tree/main/200.Web/202_SimpleWebHandleFunc
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World!")
})
http.ListenAndServe(":8080", nil)
}
001의 코드와 비교하면 훨씬 간단하게 구현된 것을 확인할 수 있다. http.HandleFunc 함수의 정의는 다음과 같다.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
http.Handle 함수와 동일한 구조로 파라미터를 그대로 DefaultServeMux.HandleFunc에 전달한다. 다음으로 DefaultServeMux.HandleFunc 메서드의 정의를 보자.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
//위의 HandlerFunc는 타입이다.
//위의 handler는 매개변수로 받은 함수이다.
}
// 아래의 HandlerFunc 타입으로 형변환할 경우 ServeHTTP 함수를 구현한 핸들러 타입이 된다.
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
DefaultServeMux.HandleFunc 메서드는 패턴(pattern)과 함수(handler)를 매개변수로 받는다. 이 두가지 매개변수를 DefaultServeMux.Handle 메서드로 전달하는데, 이 메서드는 문자열과 핸들러를 받기 때문에 일반 함수를 전달하면 에러가 발생한다. 따라서 함수를 HandlerFunc(handler) 로 타입 변환하여 전달하는 것을 볼 수 있다. HandleFunc에서 전달받은 매개변수는 변수명만 handler이지 일반 함수이다. 이 함수가 핸들러가 되려면 핸들러 인터페이스를 구현해야 한다. 즉 하나의 타입이어야 하고 ServeHTTP 메서드를 정의해야 한다.
따라서 아래의 HandlerFunc 타입을 통해 일반 함수를 핸들러 인터페이스를 구현한 타입으로 변환할 수 있다. 코드를 보면 HandlerFunc 타입을 선언하고, 해당 타입은 ServeHTTP 메서드를 선언한 것을 볼 수 있다. (핸들러 인터페이스 구현) 그리고 ServeHTTP 함수는 타입 자체를 호출하는 형태인 것을 알 수 있다. 이는 타입이 함수 형태이기 때문에 가능한 것이다. 복잡하지만 흥미로운 구현이다. 구현은 복잡하지만 실제 함수를 호출할 때 효율적으로 사용할 수 있다. 이러한 패턴을 통해 일반 함수를 매개변수로 사용하여 핸들러 함수로 멀티플렉서에 할당할 수 있다.
정리하면, http.HandleFunc(pattern string, handler func(ResponseWriter, *Request)) 함수를 통해 일반 함수를 핸들러 타입으로 변환하여 DefaultServeMux 멀티플렉서에 등록할 수 있다.
참고자료: net/http 패키지 소스코드
https://golang.org/src/net/http/server.go
내용에 오류가 있거나 질문이 있으면 댓글로 소통해주세요
언제나 환영합니다
'Go > Go Web Programming' 카테고리의 다른 글
[Go Web programming] 003.서버 설정 | 데인트리 라이브러리 (0) | 2021.06.17 |
---|---|
[Go Web programming] 001.Go로 시작하는 웹서버 | 데인트리 라이브러리 (0) | 2021.06.06 |
댓글