yoongrammer

[Go] HTTP Server 만들기 본문

언어/Go 언어

[Go] HTTP Server 만들기

yoongrammer 2020. 11. 24. 22:05
728x90

목차

    [Go] HTTP Server 만들기


    http 패키지는 HTTP 클라이언트 및 서버를 구현할 때 사용합니다.

     

    HTTP server를 만드는 방법은 4가지가 있습니다.

    1. No request parsing
    2. Manual request parsing
    3. Multiplexer
    4. Global multiplexer

    크게 Multiplexer를 사용하느냐(3, 4) 안하느냐(1, 2)로 두 가지로 나뉩니다.

    1. No request parsing


    가장 기본적인 구현 방법 입니다.

    어떤 요청이 와도 동일한 응답만 하는 서버입니다.

    package main
    
    import (
      "fmt"
      "net/http"
    )
    
    type database map[string]string
    
    func (d database) ServeHTTP(w http.ResponseWriter, r *http.Request) {
      for country, greeting := range d {
        fmt.Fprintf(w, "%s: %s\n", country, greeting)
      }
    }
    
    func main() {
      db := database{
        "kor": "안녕!",
        "eng": "Hello!",
      }
    
      http.ListenAndServe("localhost:8000", db)
    }

    요청을 받으면 인사말을 출력하는 서버입니다.

     

    ListenAndServer함수는 TCP 네트워크 주소(addr)에서 수신 대기(Listen)한 다음 통신 요청들을 받아서 Serve 함수를 통해 handler 함수를 실행시킵니다.

    func ListenAndServe(addr string, handler Handler) error

    두 번째 인자인 Handler 타입을 확인하면 아래와 같습니다.

    type Handler interface {
      ServeHTTP(ResponseWriter, *Request)
    }

    Handler는 HTTP 요청에 대한 응답을 해줍니다.

     

    HandlerServerHTTP를 가지고 있는 interface입니다. 따라서 위 코드에서 ServeHTTP 함수를 가지고 있는 database를 ListenAndServe의 두 번째 인자로 넣습니다.

     

    위에 코드는 어떤 요청에도 관계없이 항상 동일한 응답을 제공합니다.

    예)

    • http://localhost:8000/
    • http://localhost:8000/abc
    • http://localhost:8000/kor

    2. Manual request parsing


    이번에는 ServeHTTP 함수 안에서 요청되는 경로를 구문 분석하도록 메서드를 수정하겠습니다.

    package main
    
    import (
      "fmt"
      "net/http"
    )
    
    type database map[string]string
    
    func (d database) ServeHTTP(w http.ResponseWriter, r *http.Request) {
      switch r.URL.Path {
        case "/foo":
          fmt.Fprintf(w, "kor: %s\n", d["kor"])
        case "/bar":
          fmt.Fprintf(w, "eng: %s\n", d["eng"])
        default:
          http.NotFound(w,r)
      }
    }
    
    func main() {
      db := database{
        "kor": "안녕!",
        "eng": "Hello!",
      }
    
      http.ListenAndServe("localhost:8000", db)
    }

    switch 문을 사용하여 request 경로 별로 다르게 출력되도록 수정하였습니다.

    case에 없는 경로 라면 NotFound를 호출하여 "404 not found error"를 출력합니다.

     

    NotFound 함수는 "404 not found error"로 요청에 대한 응답을 하는 함수입니다.

    func NotFound(w ResponseWriter, r *Request)

    요청 결과를 확인해 보겠습니다.

    3. Multiplexer


    두 번째 방식은 케이스가 증가하면 할수록 swich 문은 복잡해지고 관리가 어렵게 됩니다.

    이번에는 ServerHTTP 메서드 대신 ServeMux를 사용하는 방법에 대해 알아보도록 하겠습니다.

     

    ServeMux는 HTTP 요청 멀티플렉서입니다. 패턴에 맞는 핸들러를 등록하고 패턴과 맞는 요청이 오면 패턴에 대한 핸들러를 호출하게 됩니다.

    package main
    
    import (
      "fmt"
      "net/http"
    )
    
    type database map[string]string
    
    func (d database) kor (w http.ResponseWriter, r *http.Request) {
      fmt.Fprintf(w, "kor: %s\n", d["kor"])
    }
    
    func (d database) eng (w http.ResponseWriter, r *http.Request) {
      fmt.Fprintf(w, "eng: %s\n", d["eng"])
    }
    
    func main() {
      db := database{
        "kor": "안녕!",
        "eng": "Hello!",
      }
    
      mux := http.NewServeMux()
    
      mux.Handle("/kor", http.HandlerFunc(db.kor))
      mux.HandleFunc("/eng", db.eng)
    
      http.ListenAndServe("localhost:8000", mux)
    }

    NewServeMux 함수를 호출하여 새로운 ServeMux를 할당받습니다.

     mux := http.NewServeMux()

     

    주어진 패턴에 대한 핸들러를 등록합니다.

    mux.Handle("/kor", http.HandlerFunc(db.kor))
    mux.HandleFunc("/eng", db.eng)

     

    HandlerFunc 은 일반 함수를 HTTP 핸들러로 사용할 수 있는 어댑터 역할을 합니다.

    type HandlerFunc func(ResponseWriter, *Request)

     

    Handle 은 주어진 패턴에 대한 핸들러를 등록하는 함수입니다.

    func Handle(pattern string, handler Handler)

     

    Handle 함수 두 번째 인자에 Handler를 넣어야 하기 때문에 일반 함수를 넣기 위해선 HandlerFunc type을 사용하여 Handler로 변환해야 합니다.

    mux.Handle("/kor", http.HandlerFunc(db.kor))

     

    HandleFunc 은 주어진 패턴에 대한 핸들러 함수를 등록하는 함수입니다.

    func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

     

    HandleFunc 함수 두 번째 인자는 변환 없이 함수를 직접 등록하면 됩니다.

    mux.HandleFunc("/eng", db.eng)

    4. Global multiplexer


    일반적으로 코드는 별도의 패키지로 분할됩니다.

    라우팅 핸들러를 설정하려면 ServeMux 인스턴스를 각 패키지로 전달해야 하는 번거로움이 있습니다.

     

    Global multiplexerDefaultServeMux 인스턴스를 사용하면 간단하게 사용할 수 있습니다.

    package main
    
    import (
      "fmt"
      "net/http"
    )
    
    type database map[string]string
    
    func (d database) kor (w http.ResponseWriter, r *http.Request) {
      fmt.Fprintf(w, "kor: %s\n", d["kor"])
    }
    
    func (d database) eng (w http.ResponseWriter, r *http.Request) {
      fmt.Fprintf(w, "eng: %s\n", d["eng"])
    }
    
    func main() {
      db := database{
        "kor": "안녕!",
        "eng": "Hello!",
      }
    
      http.Handle("/kor", http.HandlerFunc(db.kor))
      http.HandleFunc("/eng", db.eng)
    
      http.ListenAndServe("localhost:8000", nil)
    }

    ListenAndServe 함수 두 번째 인자에 nil을 넣어주면 DefaultServeMux를 사용하게 됩니다.

    http.ListenAndServe("localhost:8000", nil)

     

    HandleHandleFunc 함수를 사용하여 DefaultServeMux에 핸들러를 추가하면 됩니다.

    http.Handle("/kor", http.HandlerFunc(db.kor))
    http.HandleFunc("/eng", db.eng)

     

     

    참고: https://www.integralist.co.uk/posts/understanding-golangs-func-type/

    728x90

    '언어 > Go 언어' 카테고리의 다른 글

    [Go] Modules 알아보기  (0) 2020.12.06
    [Go] Handle, Handler , HandleFunc 이해  (0) 2020.11.26
    gdlv - Go언어 디버거  (0) 2020.11.22
    Delve - Go언어 디버거  (0) 2020.11.13
    [Go] 채널 방향, 채널 버퍼링, Select  (0) 2020.11.06
    Comments