golang - handler

한나리·2025년 8월 29일

Go

목록 보기
19/19
post-thumbnail

앞에서 "핵심 확장 포인트 = Handler"
JSON-RPC, CORS, Middleware 등은 Handler 레이어에서 구현한다고 했는데
이번엔 그럼 Server.go에 이어서 handler에 대해 알아보자.

rpc/server.go을 보면 HTTP 서버/연결 관리(Serve, serveConn, ServeHTTP)하는 코드들이 주로 있는데 여기서 핵심은 Handler.ServeHTTP()까지 요청이 들어간다.

그리고 Geth에서는 실제 JSON-RPC 요청을 처리하는 핵심 Handler가 정의되어 있다. 요청을 읽고 JSON 언마샬하고 serveCodec을 통해 등록된 메서드를 실행하여 응답을 반환한다. 여기서 ServeHTTP가 JSON-RPC 메세지를 디코딩해서 Geth 내부 메서드 호출로 이어진다.

[참고] Handler_code

Overview

// src/net/http/server.go
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

...

type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

...
var DefaultServeMux = &ServeMux{}

func Handle(pattern string, handler Handler) {
    DefaultServeMux.Handle(pattern, handler)
}

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.Handle(pattern, HandlerFunc(handler))
}
...

handler 핵심 구조

1. Handler 인터페이스

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
  • 역할 : HTTP 요청을 처리하는 표준 인터페이스
  • 필수 구현 메서드 : ServeHTTP(w http.ResponseWriter, r *http.Request)
  • 포인트 : Geth도 결국 JSON-RPC 요청을 http.Request로 받고, 이 인터페이스를 구현하는 핸들러를 등록한다.

2. HandlerFunc 어댑터

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}
  • 역할 : 단순 함수 (func(ResponseWriter, *Request))를 Handler로 변환해줌
  • 예시 :
    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello World")
    })
    • 내부적으로 HandlerFunc 타입으로 감싸져서 ServeHTTP가 호출

아직 정확하게 와닿지 않아서 추가 정리를 하자면,
Go의 HTTP서버는 기본적으로 Handler 인터페이스를 기대하는데,
보통 사용성을 위해서는

http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello World")
})

이런식으로 간단하게 함수를 쓰고자 한다.
여기서 func(responseWriter, *Request)는 인터페이스가 아니라 그냥 함수 타입이기 때문에, 서버가 바로 쓸 수 없다.

그래서 등장하는게 handlerFunc 타입이다.

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r) // 함수 자체를 호출해줌
}

HandlerFunc는 함수 → Handler 인터페이스로 변환해주는 "어댑터"가 되는 것이다.
결과적으로 HandlerFuncServeHTTP를 구현해서 Handler 인터페이스처럼 동작하게 만들어주고, http.Server는 이걸 정상적으로 호출할 수 있게 된다.

사용자 함수 (func)(HandlerFunc 어댑터로 래핑)
HandlerFunc (ServeHTTP 구현)
   ↓
Handler 인터페이스
   ↓
http.Server

3. DefaultServeMux (기본 라우터)

// DefaultServeMux is the default [ServeMux] used by [Serve].
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

// Handle registers the handler for the given pattern in [DefaultServeMux].
// The documentation for [ServeMux] explains how patterns are matched.
func Handle(pattern string, handler Handler) {
	if use121 {
		DefaultServeMux.mux121.handle(pattern, handler)
	} else {
		DefaultServeMux.register(pattern, handler)
	}
}

// HandleFunc registers the handler function for the given pattern in [DefaultServeMux].
// The documentation for [ServeMux] explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if use121 {
		DefaultServeMux.mux121.handleFunc(pattern, handler)
	} else {
		DefaultServeMux.register(pattern, HandlerFunc(handler))
	}
}
  • DefaultServeMux = 전역 라우터 (URL -> Handler 매핑)
  • http.HandleFunc는 내부적으로 HandleFunc로 변환 후 DefaultServeMux에 등록
  • 요청이 들어오면 ServeMux.ServeHTTP()에서 적절한 핸들러를 찾아 실행

4. ServeMux 동작 방식

type ServeMux struct {
	mu       sync.RWMutex
	tree     routingNode
	index    routingIndex
	patterns []*pattern  // TODO(jba): remove if possible
	mux121   serveMux121 // used only when GODEBUG=httpmuxgo121=1
}
...
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	if r.RequestURI == "*" {
		if r.ProtoAtLeast(1, 1) {
			w.Header().Set("Connection", "close")
		}
		w.WriteHeader(StatusBadRequest)
		return
	}
	var h Handler
	if use121 {
		h, _ = mux.mux121.findHandler(r)
	} else {
		h, r.Pattern, r.pat, r.matches = mux.findHandler(r)
	}
	h.ServeHTTP(w, r)
}
  • mux.Handler(r) -> 등록된 URL 패턴과 요청 URI 매칭
  • 매칭된 핸들러의 ServeHTTP 실행
  • 없으면 http.NotFoundHandler() 호출

전체 흐름 요약

Client Request → net.Listener.Accept() 
    → Server.Serve()
        → conn.serve()
            → ServeMux.ServeHTTP(w, r)
                → Handler.ServeHTTP(w, r)

즉, server.go는 HTTP 서버의 골격만 제공하고, 실제 요청 처리는 Handler에게 위임한다. 그래서 Geth는 자신만의 RPC Handler를 구현하고, 이걸 http.Server.Handler로 등록해서 JSON-RPC 요청을 처리한다.

정리하자면,

흐름

결국 http.Server가 요청을 받으면 최종적으로 Handler.ServeHTTP를 호출한다는 것인데, 그 사이에 어떤 규칙과 어댑터들이 있는지가 관건인것 같다.

모든 핸들러는 ServeHTTP를 구현하는데, 이것은 http.Server 내부에서 연결을 읽어들인 후 최종적으로 handler.ServeHTTP(rw, req)를 호출한다.

type serverHandler struct {
	srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler // 내부에서 srv.handler 또는 DefaultServeMux로 위임
	if handler == nil {
		handler = DefaultServeMux
	}
	if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" {
		handler = globalOptionsHandler{}
	}

	handler.ServeHTTP(rw, req) //최종 호출
}

또한, 평범한 함수도 Handler 처럼 쓸 수 있도록 해주는 어댑터(HandlerFunc)를 구현한다.

type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

그래서 아래가 동작할 수 있게 된다.

http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello")
}) // 내부에서 HandlerFunc로 감싸져 ServeHTTP가 호출한다.

여기서 중요한 개념 중 하나인 ServeMux는 URL-> Handler 매핑을 관리하는 기본 라우터이자 Handler자체 이다.

type ServeMux struct {
    m map[string]muxEntry // pattern → handler
    // ...
}

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    // 1) (특수) RequestURI == "*" && OPTIONS 처리
    // 2) 패턴 매칭으로 핸들러 선택
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}

ServeMux.ServeHTTP(w, r)를 타고 들어온 요청은 결국 매핑된 handler를 찾아 h.ServeHTTP(w, r)가 최종 호출된다. 결국 http.Server가 실제로 호촐하는 것은 serverHandler{srv}.ServeHTTP(=h.ServeHTTP(w, r))가 된다.
대략,
1. srv.Handlernil이면 DefaultServeMux 사용.
2. "OPTIONS *" 같은 일반 OPTIONS 요청을 기본 처리(서버 옵션에 따라 자동 200 응답 또는 위임).
3. (HTTP/2, 컨텍스트, 로깅 등) 서버 레벨에서 필요한 공통 처리를 한 뒤 최종 Handler.ServeHTTP 호출.

즉, serverHandler는 서버 레벨 공통 처리 → 최종 핸들러 호출로 위임하는 얇은 래퍼인 것이다.

Handler 확장

목적에 맞는 Http 통신을 위해 사용자가 구현해야하는 실제 Handler는 다른 Handler를 감싸서 확장하는 패턴(미들웨어 패턴 또는 핸들러 체인)이 일반적이라고 한다.

예를 들면,

func Logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

mux := http.NewServeMux()
mux.HandleFunc("/hello", hello)
handler := Logging(mux) // 체인
srv := &http.Server{Handler: handler}

이 방식을 그대로 응용하면 TPS 제한, 인증, CORS, VHost등을 같은 레이어에서 구현할 수 있다.

연결/동시성 상식

  • HTTP/1.1: 한 연결에서 요청은 순차 처리(keep-alive로 여러 요청을 연속 처리 가능).
    여러 클라이언트/연결은 각각 고루틴으로 병렬 처리.
  • HTTP/2: 같은 TCP 연결에서 요청이 멀티플렉싱되어 동시 처리 가능(HTTP/2 지원이 켜져 있으면 내부에서 처리).

정리

  • http.Server는 요청을 읽고, 최종적으로 Handler.ServeHTTP를 호출한다.
  • ServeMux는 패턴 매칭/리다이렉트/호스트 기반 매칭까지 책임지는 기본 라우터이자 핸들러이다.
  • HandlerFunc를 통해 함수도 핸들러처럼 쓸 수 있다.
  • 미들웨어는 핸들러를 감싸는 함수로 체인 형태로 구성한다.
  • 이 토대 위에 Geth는 자체 JSON-RPC 핸들러를 구현해 http.Server.Handler로 등록한다.
profile
내가 떠나기 전까지는 망하지 마라, 블록체인 개발자

0개의 댓글