Go로 웹 서버 처음부터 구축하기
Daniel Hayes
Full-Stack Engineer · Leapcell

Go로 웹 서버 처음부터 구축하기
Go에서 웹 서버를 작성하는 몇 가지 방법
I. 웹 서버와 HTTP 서버의 차이점
HTTP 서버는 이름에서 알 수 있듯이 HTTP 프로토콜을 지원하는 서버입니다. 반면에 웹 서버는 HTTP 프로토콜을 지원하는 것 외에도 다른 네트워크 프로토콜도 지원할 수 있습니다. 이 문서에서는 Golang의 공식 패키지를 사용하여 웹 서버를 작성하는 몇 가지 일반적인 방법을 소개하는 데 중점을 둡니다.
II. 가장 간단한 HTTP 서버
다음은 웹 서버를 구현하는 가장 간단한 방법입니다. 다음은 샘플 코드입니다.
package main import ( "fmt" "log" "net/http" ) func myHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello there!\n") } func main() { http.HandleFunc("/", myHandler) // 액세스 경로 설정 log.Fatal(http.ListenAndServe(":8080", nil)) }
이 프로그램을 시작한 후 다른 터미널을 열고 curl localhost:8080
명령을 입력하거나 브라우저에서 직접 localhost:8080
을 열면 출력 결과 Hello there!
가 표시됩니다.
ListenAndServe
함수의 기능은 연결을 수신하고 처리하는 것입니다. 내부 처리 방법은 각 연결에 대해 고루틴을 시작하여 처리하는 것입니다. 그러나 이것은 이상적인 처리 방법이 아닙니다. 운영 체제를 공부한 학생들은 프로세스 또는 스레드 전환 비용이 엄청나다는 것을 알고 있습니다. 고루틴은 사용자 수준의 경량 스레드이고 스위칭해도 사용자 모드와 커널 모드 간의 스위치가 발생하지 않지만 고루틴 수가 많으면 스위칭 비용이 여전히 무시할 수 없습니다. 더 나은 방법은 고루틴 풀을 사용하는 것이지만 이 기사에서는 지금은 자세히 설명하지 않습니다.
III. 핸들러 인터페이스 사용
이전 방법은 확장성이 떨어집니다. 예를 들어 서버의 시간 제한을 설정할 수 없습니다. 이때 사용자 지정 서버를 사용할 수 있습니다.
Server
구조체는 다음과 같이 정의됩니다.
type Server struct { Addr string // 수신 대기할 TCP 주소 Handler Handler // 호출할 핸들러 ReadTimeout time.Duration // 요청 읽기 시간 초과 전 최대 기간 WriteTimeout time.Duration // 응답 쓰기 시간 초과 전 최대 기간 TLSConfig *tls.Config // ... }
Handler
는 다음과 같이 정의된 인터페이스입니다.
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
따라서 Handler
인터페이스의 ServeHTTP
메서드를 구현하는 한 서버를 사용자 정의할 수 있습니다. 샘플 코드는 다음과 같습니다.
package main import ( "log" "net/http" "time" ) type myHandler struct{} func (this myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // 특정 처리 로직 } func main() { server := http.Server{ Addr: ":8080", Handler: &myHandler{}, ReadTimeout: 3 * time.Second, // ... } log.Fatal(server.ListenAndServe()) }
IV. conn을 직접 처리하기
때로는 더 기본적인 수준에서 연결을 처리해야 할 수도 있습니다. 이때 net
패키지를 사용할 수 있습니다. 서버 측 코드는 다음과 같이 간단하게 구현됩니다.
package main import ( "log" "net" ) func handleConn(conn net.Conn) { // 연결 처리의 특정 로직 } func main() { listener, err := net.Listen("tcp", ":8080") if err != nil { log.Fatal(err) } for { conn, err := listener.Accept() if err != nil { // 오류 처리 } go handleConn(conn) } }
예제의 편의를 위해 여기서는 각 연결에 대해 고루틴을 시작하여 처리합니다. 실제로 사용할 때는 일반적으로 고루틴 풀을 사용하는 것이 더 나은 선택입니다. 클라이언트에 대한 반환 정보는 conn
에 다시 쓰기만 하면 됩니다.
V. 프록시
이전 섹션에서는 웹 서버를 구현하는 몇 가지 방법을 소개했습니다. 이제 간단하게 HTTP 프록시를 구현해 보겠습니다. Golang에서 프록시를 작성하는 것은 매우 간단합니다. 연결을 전달하기만 하면 됩니다.
package main import ( "io" "log" "net" ) func handleConn(from net.Conn) { to, err := net.Dial("tcp", ":8001") // 대상 서버와의 연결 설정 if err != nil { // 오류 처리 } done := make(chan struct{}) go func() { defer from.Close() defer to.Close() io.Copy(from, to) done <- struct{}{} }() go func() { defer from.Close() defer to.Close() io.Copy(to, from) done <- struct{}{} }() <-done <-done } func main() { listener, err := net.Listen("tcp", ":8080") if err != nil { log.Fatal(err) } for { conn, err := listener.Accept() if err != nil { // 오류 처리 } go handleConn(conn) } }
프록시를 약간 개선하면 더 많은 기능을 구현할 수 있습니다.
Leapcell: Golang 앱 호스팅을 위한 차세대 서버리스 플랫폼
마지막으로 Go 서비스를 배포하는 데 가장 적합한 플랫폼인 **Leapcell**을 추천하고 싶습니다.
1. 다국어 지원
- JavaScript, Python, Go 또는 Rust로 개발합니다.
2. 무료로 무제한 프로젝트 배포
- 사용량에 대해서만 지불합니다. 요청도 없고 요금도 없습니다.
3. 최고의 비용 효율성
- 유휴 요금 없이 사용량에 따라 지불합니다.
- 예: $25는 평균 응답 시간 60ms에서 694만 건의 요청을 지원합니다.
4. 간소화된 개발자 경험
- 간편한 설정을 위한 직관적인 UI
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합
- 실행 가능한 통찰력을 위한 실시간 메트릭 및 로깅
5. 간편한 확장성 및 고성능
- 고도의 동시성을 쉽게 처리할 수 있도록 자동 확장
- 운영 오버헤드가 전혀 없습니다. 구축에만 집중하세요.
Leapcell Twitter: https://x.com/LeapcellHQ