golang - server.go

한나리·2025년 8월 28일

Go

목록 보기
18/19
post-thumbnail

Overview

[참고] server.go

type Server struct {
	// Addr는 서버가 바인딩할 TCP 주소를 지정합니다.
	// 형식: "host:port". 비어있으면 ":http"(포트 80) 사용.
	// 서비스 이름은 RFC 6335에 정의되어 있고 IANA에서 할당합니다.
	Addr string

	// Handler는 요청 처리 핸들러입니다. nil이면 http.DefaultServeMux 사용
	Handler Handler

	// DisableGeneralOptionsHandler가 true면 "OPTIONS *" 요청을 Handler로 전달
	// false면 200 OK와 Content-Length: 0으로 자동 응답
	DisableGeneralOptionsHandler bool

	// TLSConfig는 ServeTLS와 ListenAndServeTLS에서 사용할 TLS 설정
	TLSConfig *tls.Config

	// ReadTimeout은 요청 전체를 읽는 최대 시간 (body 포함)
	// 0 또는 음수 = 제한 없음
	ReadTimeout time.Duration

	// ReadHeaderTimeout은 요청 헤더를 읽는 최대 시간
	// 0이면 ReadTimeout 사용, 음수이면 제한 없음
	ReadHeaderTimeout time.Duration

	// WriteTimeout은 응답 쓰기 최대 시간
	// 0 또는 음수 = 제한 없음
	WriteTimeout time.Duration

	// IdleTimeout은 keep-alive 연결에서 다음 요청을 기다리는 최대 시간
	// 0이면 ReadTimeout 사용
	IdleTimeout time.Duration

	// MaxHeaderBytes는 서버가 읽을 요청 헤더 최대 바이트 수
	// body 크기는 제한하지 않음. 0이면 DefaultMaxHeaderBytes 사용
	MaxHeaderBytes int

	// TLSNextProto는 ALPN 프로토콜 업그레이드 시 TLS 연결을 직접 처리하는 함수
	TLSNextProto map[string]func(*Server, *tls.Conn, Handler)

	// ConnState는 클라이언트 연결 상태 변경 시 호출되는 콜백 함수
	ConnState func(net.Conn, ConnState)

	// ErrorLog는 연결 수락, 핸들러 오류, 파일 시스템 오류 등을 기록할 Logger
	ErrorLog *log.Logger

	// BaseContext는 서버 시작 시 요청의 기본 context를 반환하는 함수
	BaseContext func(net.Listener) context.Context

	// ConnContext는 새 연결에서 사용할 context를 수정하는 함수
	ConnContext func(ctx context.Context, c net.Conn) context.Context

	// HTTP2 설정
	HTTP2 *HTTP2Config

	// Protocols는 서버가 수락하는 프로토콜 집합
	Protocols *Protocols

	inShutdown atomic.Bool // 서버가 종료 중일 때 true

	disableKeepAlives atomic.Bool
	nextProtoOnce     sync.Once // HTTP/2 초기화 보호
	nextProtoErr      error     // http2.ConfigureServer 결과

	mu         sync.Mutex
	listeners  map[*net.Listener]struct{}
	activeConn map[*conn]struct{}
	onShutdown []func()

	listenerGroup sync.WaitGroup
}
필드역할
Addr서버 바인딩 주소
Handler실제 요청 처리 핸들러
DisableGeneralOptionsHandlerOPTIONS 요청 처리 방식 제어
TLSConfigTLS/HTTPS 설정
ReadTimeout / ReadHeaderTimeout / WriteTimeout / IdleTimeout요청/응답/유휴 연결 타임아웃
MaxHeaderBytes헤더 최대 크기 제한
TLSNextProtoALPN 업그레이드 시 TLS 연결 커스터마이징
ConnState연결 상태 변화 콜백
ErrorLog에러 로그 기록
BaseContext / ConnContext요청/연결 context 설정
HTTP2 / ProtocolsHTTP/2 및 프로토콜 설정
inShutdown / disableKeepAlives / nextProtoOnce / nextProtoErr내부 상태 관리
mu / listeners / activeConn / onShutdown / listenerGroup동시성 관리, 연결 추적, 종료 처리

포인트 요약

Server 구조체 = Go 표준 HTTP 서버 핵심 엔진

핸들러(Handler)만 교체하면 JSON-RPC, REST 등 다양한 서버 구현 가능

Timeout, TLS, HTTP2, 연결 상태, 에러 로깅 등 거의 모든 서버 동작 옵션 제공

내부 필드(mu, activeConn 등)는 동시성 안전과 종료 처리를 위해 사용

Server.go 핵심 구조

Server struct
├─ Addr           : 서버 바인딩 주소
├─ Handler        : 요청 처리 인터페이스 (http.Handler)
├─ ReadTimeout    : 요청 읽기 타임아웃
├─ WriteTimeout   : 응답 쓰기 타임아웃
├─ MaxHeaderBytes : 최대 헤더 크기
└─ 기타 설정들

Server.ListenAndServe()
└─ net.Listen("tcp", Addr)
   └─ srv.Serve(listener)

Server.Serve(l net.Listener)
├─ 무한 루프: for {
│    └─ l.Accept() → net.Conn (새 연결 수락)
│         ├─ 연결 검사 및 상태 관리(connState)
│         ├─ keep-alive 처리
│         └─ go srv.serveConn(c) (고루틴으로 연결 처리)
└─ }

Server.serveConn(c net.Conn)
├─ 요청 읽기: readRequest()
│    └─ HTTP 요청 파싱 (Method, URL, Header, Body)
├─ 타임아웃 적용 (ReadTimeout)
├─ 요청 처리: Handler.ServeHTTP(w, r)
│    └─ 사용자가 정의한 Handler 호출
├─ 응답 전송: writeResponse()
├─ 연결 상태 업데이트 & keep-alive 확인
└─ 연결 종료 또는 재사용

Server.ServeHTTP(w http.ResponseWriter, r *http.Request)
└─ 기본적으로 Handler.ServeHTTP 호출
   └─ 실제 로직은 사용자가 지정한 Handler에서 구현
  • Server 구조체 = HTTP 서버 설정과 핸들러 보관
  • ListenAndServe / Serve = TCP 소켓 바인딩 + 연결 수락 + serveConn 호출
  • serveConn = 개별 연결 처리 루틴
    요청 읽기 → Handler 호출 → 응답 쓰기 → 연결 상태 관리
  • 핵심 확장 포인트 = Handler
    JSON-RPC, CORS, Middleware 등은 Handler 레이어에서 구현

즉, server.go 자체는 순수 HTTP 서버 엔진이고,
JSON-RPC 같은 기능은 여기서 직접 구현하지 않고 Handler로 붙이는 구조

Client Request
      │
      ▼
TCP Connection Accept (net.Listener.Accept)
      │
      ▼
Server.Serve()
  ├─ 무한 루프: 연결 수락
  ├─ ConnState 콜백 호출 (State: new)
  └─ go Server.serveConn(c) (각 연결 별 고루틴)
         │
         ▼
  Server.serveConn(c)
  ├─ readRequest() → 요청 파싱 (Method, URL, Header, Body)
  ├─ ReadTimeout / ReadHeaderTimeout 적용
  ├─ idleTimeout / keep-alive 관리
  ├─ Handler.ServeHTTP(w, r) 호출
  │      └─ 실제 요청 처리 (JSON-RPC, REST 등)
  ├─ writeResponse() → 응답 전송
  ├─ ConnState 콜백 호출 (State: active / idle / closed)
  └─ 연결 재사용 또는 종료
  1. TCP 연결 수락 → Server.Serve
  2. 각 연결 별 고루틴 → serveConn
  3. 요청 읽기 & 파싱 → readRequest
  4. 핸들러 호출 → Handler.ServeHTTP
  5. 응답 전송 → writeResponse
  6. keep-alive / ConnState 관리 → 재사용 혹은 종료

코드로 확인하면,

func (srv *Server) Serve(l net.Listener) error {
	if fn := testHookServerServe; fn != nil {
		fn(srv, l) // call hook with unwrapped listener
	}

	// 1. Listener 래핑
	origListener := l
	l = &onceCloseListener{Listener: l}
	defer l.Close() //서버 종료 시 안전하게 닫도록 래핑

	// 2. HTTP/2 초기화 (HTTP/2 지원 준비)
	if err := srv.setupHTTP2_Serve(); err != nil {
		return err
	}

	// 3. Listener 추적 - 연결 관리 및 종료 시 정리
	if !srv.trackListener(&l, true) {
		return ErrServerClosed
	}
	defer srv.trackListener(&l, false)

	// 4. 기본 Context 준비 - 모든 요청에서 사용할 기본 Context 생성
	baseCtx := context.Background()
	if srv.BaseContext != nil {
		baseCtx = srv.BaseContext(origListener)
		if baseCtx == nil {
			panic("BaseContext returned a nil context")
		}
	}

	var tempDelay time.Duration // how long to sleep on accept failure

	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	//5. Accept 루프
    for {
		rw, err := l.Accept()
		if err != nil {
			if srv.shuttingDown() {
				return ErrServerClosed
			}
			if ne, ok := err.(net.Error); ok && ne.Temporary() {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return err
		}
		connCtx := ctx
		if cc := srv.ConnContext; cc != nil {
			connCtx = cc(connCtx, rw)
			if connCtx == nil {
				panic("ConnContext returned nil")
			}
		}
		tempDelay = 0 // 임시 요류 시 재시도
		c := srv.newConn(rw) // 새 TCP 연결 수락
		c.setState(c.rwc, StateNew, runHooks) // before Serve can return
        //연결별 고루틴(serve)에서 요청 처리
        //여기서 readRequest() -> Handler.ServeHTTP 호출 -> writeResponse() 흐름으로 들어감 //c는 conn 타입으로, TCP 연결을 감싸는 구조체
		go c.serve(connCtx) 
        
	}
}

이 부분은, Server 구조체에서 TCP 연결을 받아서 요청 처리 고루틴을 생성하는 엔진이다. 요청이 Handler까지 도달하는 전체 흐름의 첫 단계에 해당한다.
이 부분이 ServeHTTP -> Handler 호출까지 이어지는 루프를 담당하고 있다.

즉, 위 흐름 정리한 부분에서 "TCP 연결 수락 -> serveConn -> 요청 읽기 -> Handler 호출"의 첫 단계가 바로 이 Serve 함수이다.

"go c.serve(connCtx)" 이 부분을 더 들어가보면,


// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
	...
    
	for {
    	//HTTP 요청 파싱
		w, err := c.readRequest(ctx)
		...
        
        //ServeHTTP 호출 - 실제 사용자가 지정한 Handler가 요청을 처리
		serverHandler{c.server}.ServeHTTP(w, w.req)
		...
	}
}

결국 Server(...) -> server(...)를 통해 Request를 파싱해서 실제 사용자 지정 Handler로 요청을 처리하도록 연결시켜준다.
위에 언급했듯 Handler가 핵심 확장 포인트인 이유이다.

Geth도 Go 표준 http.Server의 Handler 인터페이스를 구현해서 JSON-RPC 서버를 만든다.

srv := &http.Server{
    Addr:    ":8545",
    Handler: rpcServer, // <- 여기서 Handler 구현체를 줌
}

Geth의 RPC 서버는,

  • rpc.NewServer() 같은 함수로 RPC 서버 객체를 생성하고
  • 이 객체가 ServeHTTP(w http.ResponseWriter, r *http.Request)를 구현하도록 하고
  • JSON-RPC 요청을 처리한다.
    • HTTP body에서 JSON 읽기
    • "method"에 맞는 RPC 함수 호출
    • 결과를 JSON으로 응답

즉, Geth에서 Server.Handler는 JSON-RPC 전용 Handler로 교체된 상태인 것이다.

TCP 연결
   │
   ▼
Server.Serve → conn.serve
   │
   ▼
c.handler().ServeHTTP(rw, req)  ← 여기서 Geth RPC Handler 호출
   │
   └─ JSON 파싱 → 해당 RPC 메서드 호출 → JSON 응답

일반 Go 서버는 Handler가 http.DefaultServeMux이지만 Geth는 RPC 서버 객체가 Handler를 구현하여 JSON-RPC 처리한다.

Geth JSON-RPC 요청 처리 흐름

정리하면 Geth JSON-RPC 요청 처리 흐름은 아래와 같다.

Client (HTTP request)
   │
   ▼
TCP Connection Accept
   │
   ▼
Server.Serve(listener)
   │
   ├─ 연결 래핑, BaseContext/ConnContext 설정
   └─ go conn.serve(connCtx)  // 연결별 고루틴
          │
          ▼
    conn.serve()
          │
          ├─ readRequest()   // HTTP 요청 읽기 & 파싱
          │
          └─ c.handler().ServeHTTP(rw, req)  // Handler 호출
                     │
                     ▼
           serverHandler{Server}.ServeHTTP
                     │
                     ▼
           Geth RPC Handler ServeHTTP 구현
                     │
                     ├─ JSON body 파싱
                     ├─ "method" 찾기
                     ├─ RPC 함수 실행
                     └─ 결과를 JSON으로 ResponseWriter에 씀
  1. Server.Serve

    • TCP 연결 수락, 고루틴 생성
    • BaseContext, ConnContext, Keep-Alive 등 처리
  2. conn.serve

    • HTTP 요청 파싱(readRequest)
    • Handler 호출
  3. Server.Handler

    • 일반 Go 서버: http.DefaultServeMux
    • Geth: JSON-RPC Handler 구현체
      → 요청 body 파싱 → RPC 메서드 호출 → JSON 응답
profile
내가 떠나기 전까지는 망하지 마라, 블록체인 개발자

0개의 댓글