본문 바로가기
개발

Go 언어로 간단한 리버스 프록시 서버 구축하기

by hes3518 2024. 11. 12.
728x90
반응형

Go 언어로 만드는 간단한 리버스 프록시 서버

안녕하세요! 프로그래밍의 매력을 느끼고 있는 여러분, 오늘은 Go 언어를 이용하여 간단한 리버스 프록시 서버를 구축하는 방법을 알아보겠습니다. 특히, Go의 생생한 패키지들을 활용해 일반적인 프록시 서버의 역할을 하는 코드를 만들어볼 예정입니다.

들어가며

리버스 프록시 서버란, 클라이언트와 서버 사이에서 클라이언트의 요청을 다른 서버로 전달하고, 그 응답을 클라이언트에게 반환하는 역할을 합니다. 웹사이트의 트래픽 증가에 대처하기 위한 엔지니어링 방법 중 하나로, 리버스 프록시가 많은 비즈니스에서 사용되고 있습니다.

코드 작성하기

아래는 Go 언어의 표준 라이브러리를 사용해 리버스 프록시 서버를 구현한 코드입니다.

1. 프로젝트 파일 구조

.
├── go.mod
├── main.go
├── handler
│   ├── default_handler.go
│   └── proxy_handler.go
└── server
    └── server.go

2. main.go

package main

import (
   "fmt"
   "my-go-proxy-server/server"
)

func main() {
   fmt.Println("proxy-server start")
   server.InitProxyServer()
}

이 코드는 프로그램의 진입점으로, InitProxyServer() 함수를 호출하여 서버를 시작합니다.

3. server.go

package server

import (
   "my-go-proxy-server/handler"
   "net/http"
)

func InitProxyServer() {
   mux := http.NewServeMux()
   handler.InitDefaultHandler(mux)
   handler.InitProxyHandler(mux)
   err := http.ListenAndServe(":8081", mux)
   if err != nil {
      panic(err)
   }
}

여기에서는 HTTP 요청을 처리할 ServeMux 인스턴스를 생성한 후, 기본 핸들러와 프록시 핸들러를 추가합니다.

4. default_handler.go

package handler

import (
   "fmt"
   "net/http"
)

func InitDefaultHandler(mux *http.ServeMux) {
   mux.HandleFunc("/health", health)
}

func health(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintf(w, "hello~")
}

기본 핸들러는 /health 경로로의 요청에 대한 응답을 정의합니다.

5. proxy_handler.go

package handler

import (
   "context"
   "crypto/tls"
   "fmt"
   "log"
   "net"
   "net/http"
   "net/http/httputil"
   "net/url"
   "strings"
)

func InitProxyHandler(mux *http.ServeMux) {
   mux.HandleFunc("/", handleProxy)
}

func handleProxy(w http.ResponseWriter, r *http.Request) {
   host := getHost(r)
   path := r.URL.Path
   fmt.Printf("host: [%s], path: [%s]
", host, path)

   var target *url.URL
   if path == "/" {
      target, _ = url.Parse("https://example.com") // redirect to your preferred target
   } else {
      u := fmt.Sprintf("https://example.com/search?q=%s", path[1:]) // sample search URL format
      target, _ = url.Parse(u)
   }

   reverseProxy := httputil.NewSingleHostReverseProxy(target)
   reverseProxy.Transport = &http.Transport{
      DialTLSContext: dialTLS,
      TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
   }
   reverseProxy.ServeHTTP(w, r)
}

func getHost(r *http.Request) string {
   i := strings.Index(r.Host, ":")
   if i > 0 {
      return r.Host[:i]
   }
   return r.Host
}

func dialTLS(ctx context.Context, network, addr string) (net.Conn, error) {
   conn, err := net.Dial(network, addr)
   if err != nil {
      return nil, err
   }

   host, _, err := net.SplitHostPort(addr)
   if err != nil {
      return nil, err
   }
   cfg := &tls.Config{ServerName: host}

   tlsConn := tls.Client(conn, cfg)
   if err := tlsConn.Handshake(); err != nil {
      conn.Close()
      return nil, err
   }
   return tlsConn, nil
}

위의 핸들러는 기본 / 경로에 대한 모든 요청을 처리하며, 특정 경로에 따라 요청을 나누어 다른 URL로 전송합니다.

예제 실행 결과

서버를 실행한 후, http://localhost:8081로 접근하면 설정한 도메인으로 리다이렉트됩니다.

추가적으로

이 코드에서는 기본적인 리버스 프록시 기능만 제공하므로 다음과 같은 기능을 추가할 수 있습니다:

  • 에러 핸들링 향상
  • 로깅 기능 추가
  • 다양한 URL 패턴에 대한 포워딩 정책 추가

마무리하며

이번 포스트에서는 Go 언어로 리버스 프록시 서버를 구축하는 방법에 대해 알아보았습니다. 이 프록시 서버는 많은 실무 환경에서 유용하게 사용될 수 있습니다. 여러분도 직접 코드로 실습해 보며 더 깊이 있는 이해를 해보시기 바랍니다! 질문은 언제든지 댓글로 남겨주세요.

이 정보가 도움이 되었다면 공유해주시길 바랍니다! 다음 포스트에서 또 만나요!

728x90
반응형