본문 바로가기
Go/Go Web Programming

[Go Web programming] 002.핸들러와 멀티플렉서 | 데인트리 라이브러리

by 데인트리 2021. 6. 15.

 

 

 

이 페이지에서는 핸들러와 멀티플렉서 개념 및 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

 

daintree-henry/studygo

Go 스터디 자료입니다. Contribute to daintree-henry/studygo development by creating an account on GitHub.

github.com

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

 

src/net/http/server.go - The Go Programming Language

 

golang.org

 

 

내용에 오류가 있거나 질문이 있으면 댓글로 소통해주세요

언제나 환영합니다

댓글