[참고] 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 | 실제 요청 처리 핸들러 |
| DisableGeneralOptionsHandler | OPTIONS 요청 처리 방식 제어 |
| TLSConfig | TLS/HTTPS 설정 |
| ReadTimeout / ReadHeaderTimeout / WriteTimeout / IdleTimeout | 요청/응답/유휴 연결 타임아웃 |
| MaxHeaderBytes | 헤더 최대 크기 제한 |
| TLSNextProto | ALPN 업그레이드 시 TLS 연결 커스터마이징 |
| ConnState | 연결 상태 변화 콜백 |
| ErrorLog | 에러 로그 기록 |
| BaseContext / ConnContext | 요청/연결 context 설정 |
| HTTP2 / Protocols | HTTP/2 및 프로토콜 설정 |
| inShutdown / disableKeepAlives / nextProtoOnce / nextProtoErr | 내부 상태 관리 |
| mu / listeners / activeConn / onShutdown / listenerGroup | 동시성 관리, 연결 추적, 종료 처리 |
Server 구조체 = Go 표준 HTTP 서버 핵심 엔진
핸들러(Handler)만 교체하면 JSON-RPC, REST 등 다양한 서버 구현 가능
Timeout, TLS, HTTP2, 연결 상태, 에러 로깅 등 거의 모든 서버 동작 옵션 제공
내부 필드(mu, activeConn 등)는 동시성 안전과 종료 처리를 위해 사용
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에서 구현
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)
└─ 연결 재사용 또는 종료
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 함수이다.
// 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)를 구현하도록 하고"method"에 맞는 RPC 함수 호출즉, 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 요청 처리 흐름은 아래와 같다.
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에 씀
Server.Serve
conn.serve
Server.Handler
http.DefaultServeMux