
이번 글에서는 main파일인 routeserver.go에 대해 살펴보도록 하겠습니다
이 글에서는 코드를 필요한 부분만 가져와 설명하거나, 설명을 위해 배치를 임의로 변경하기도 합니다.
원본 코드는 https://github.com/pagefaultgames/rogueserver.git 에서 확인하실 수 있습니다.
Go웹서버의 가장 기본이 되는 코드는 다음과 같습니다.
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)
}

http.HandleFunc를 통해 요청에 대한 핸들러를 매핑하고, http.ListenAndServe를 통해 서버를 실행하면 웹서버가 실행됩니다.
http.ListenAndServe의 내부를 살펴보면 net.Listen으로 리스너를 만들고, s.Serve를 실행할때 인자로 리스너를 넘깁니다.

(Server의 Serve와 http의 Serve는 다르긴 하지만) routeserver.go 코드도 마찬가지로 리스너를 만들고 http.Serve를 실행하는 구조입니다.

net패키지의 Listen은 network와 address를 인자로 받아 Listener를 반환하는 메서드입니다.
func Listen(network, address string) (Listener, error) {
var lc ListenConfig
return lc.Listen(context.Background(), network, address)
}
이렇게 생성된 Listener는 address에서 network연결을 대기합니다. net.Listen("tcp", "localhost:8080")은 localhost:8080에서 TCP연결을 수신하는 Listener를 생성합니다.
// A Listener is a generic network listener for stream-oriented protocols.
//
// Multiple goroutines may invoke methods on a Listener simultaneously.
type Listener interface {
// Accept waits for and returns the next connection to the listener.
Accept() (Conn, error)
// Close closes the listener.
// Any blocked Accept operations will be unblocked and return errors.
Close() error
// Addr returns the listener's network address.
Addr() Addr
}
Listener의 핵심 메서드는 Accept입니다. Accept는 연결 요청을 기다리다가(block 상태로 대기) 요청이 들어오면 새로운 net.Conn을 반환하는 역할을 수행합니다. 하나의 Listener가 해당 주소 및 프로토콜로 들어오는 모든 연결 요청을 전담해 처리합니다.
이렇듯 Listener의 역할은 클라이언트의 요청을 받아들여 커넥션을 반환하는 것입니다. 이 Listener를 활용하는 곳은 server.Serve입니다.

http.Server는 'Listener의 Accept를 호출해(rw,err := l.Accept()) 사용자의 요청을 대기하고, 요청이 들어오면 Accept가 반환한 커넥션을 독립적인 gorountine으로 처리(go c.serve(connCtx))하는 작업'을 무한히 반복(for{})합니다.
func createListener(proto, addr string) (net.Listener, error) {
if proto == "unix" {
os.Remove(addr)
}
listener, err := net.Listen(proto, addr)
if err != nil {
return nil, err
}
if proto == "unix" {
if err := os.Chmod(addr, 0777); err != nil {
listener.Close()
return nil, err
}
}
return listener, nil
}
listener, err := net.Listen(proto, addr)을 통해 리스너를 생성하고 있으며 프로토콜이 unix일 경우에 대한 처리만 별도로 진행하고 있습니다.
http.NewServeMux는 ServeMux를 반환합니다.

ServeMux는 HTTP request multiplexer입니다. ServeMux는 클라이언트로부터 들어오는 각 요청 URL을 등록된 패턴 목록과 비교하여, URL과 가장 정확히 일치하는 패턴의 핸들러를 호출합니다. 이러한 동작은 ServeMux의 ServeHTTP메서드에 정의되어 있습니다.
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)
}
이 ServeHTTP메서드는 ServeMux만의 메서드가 아닌 Handler 인터페이스의 메서드입니다. ServeMux는 ServeHTTP메서드를 구현하고 있으므로 ServeMux는 Handler의 구현체가 될 수 있습니다. (덕타이핑)

mux := http.NewServeMux()
// init api
if err := api.Init(mux); err != nil {
log.Fatal(err)
}
포켓로그 코드는 mux핸들러를 api.Init메서드에 인자로 전달합니다. api.Init메서드에 핸들러 매핑이 정의되어 있습니다.

포켓로그의 서버는 debug변수 유무에 따라 prodHandler 또는 debugHandler를 선택해 사용합니다.
// start web server
handler := prodHandler(mux, gameurl)
if debug {
handler = debugHandler(mux)
}
func prodHandler(router *http.ServeMux, clienturl string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST")
w.Header().Set("Access-Control-Allow-Origin", clienturl)
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
router.ServeHTTP(w, r)
})
}
prodHandler는 정해진 Header 및 Method에 대한 요청만 허락합니다. 또한 clienturl에 대한 교차 출처 리소스 공유를 허용합니다. clienturl로 들어오는 값은 gameurl이며 기본값은 https://pokerogue.net입니다.
반면 debugHandler는 모든 요청(*)에 대해 허용합니다.
func debugHandler(router *http.ServeMux) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Headers", "*")
w.Header().Set("Access-Control-Allow-Methods", "*")
w.Header().Set("Access-Control-Allow-Origin", "*")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
router.ServeHTTP(w, r)
})
}
CORS설정을 처리한 뒤 router.ServeHTTP()를 통해 원래의 메인 핸들러를 호출합니다.
포켓로그의 routeserver.go는 Go의 기본적인 웹서버 실행 절차를 따르고 있어 이를 중심으로 전체 코드를 분석했습니다.