[The Go Programming Language] 1장 튜토리얼 - 1.7 웹 서버

in kr-dev •  6 years ago 

modolee_logo
안녕하세요. 개발자 모도리입니다.
The Go Programming Language 라는 책으로 Go를 공부하고 있으며, 해당 책의 내용을 요약 정리해서 올리려고 합니다. 저는 번역본을 구매해서 공부하고 있습니다.

지난 게시물


1장 튜토리얼

1.7 웹 서버

웹 서버 구현 1

  • 서버에 접근하는 데 사용된 URL의 경로를 반환하는 최소한의 서버를 작성합니다.
// Server1은 최소한의 "echo" 서버입니다.
package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", handler) // 각 요청은 핸들러를 호출합니다.
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

// handler는 요청된 URL r의 Path 구성 요소를 반환합니다.
func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}

예제코드 [ch1/server1.go]

실행결과
$ go run ch1/server1.go

  • 브라우저를 열어서 localhost:8000 에 접속하면 URL.Path = "/" 라는 메세지를 확인할 수 있습니다.
  • localhost:8000/[path] : path 부분에 임의의 주소를 넣을 경우 해당 주소를 출력합니다.
    server1_01.png
  • main 함수는 모든 URL을 의미하는 /로 시작하는 URL에 핸들러 함수를 연결하고 8000번 포트로 들어오는 요청을 처리하는 서버를 시작합니다.
  • 요청은 http.Request 구조체로 표현되며, 그 안에 들어온 요청의 URL을 비롯한 다수의 관련 필드가 있습니다.
  • 요청이 도착하면 핸들러 함수로 넘기고, 핸들러는 요청 URL에서 경로 구성요소를 추출하고 fmt.Fprintf를 이용해 응답으로 돌려줍니다.
  • 웹 서버는 7.7절에서 자세히 다룹니다.

웹 서버 구현 2

  • /count URL로 요청하면 /count로의 요청을 제외하고 지금까지 요청 된 수를 반환하는 서버를 작성합니다.
// Server2는 최소한의 "echo" 및 카운터 서버입니다.
package main

import (
    "fmt"
    "log"
    "net/http"
    "sync"
)

var mu sync.Mutex
var count int

func main() {
    http.HandleFunc("/", handler)
    http.HandleFunc("/count", counter)
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

// 핸들러는 요청된 URL의 Path 구성 요소를 반환합니다.
func handler(w http.ResponseWriter, r *http.Request) {
    mu.Lock()
    count++
    mu.Unlock()
    fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}

// counter는 지금까지 요청된 수를 반환합니다.
func counter(w http.ResponseWriter, r *http.Request) {
    mu.Lock()
    fmt.Fprintf(w, "Count %d\n", count)
    mu.Unlock()
}

예제코드 [ch1/server2.go]

실행결과
$ go run ch1/server2.go

  • 브라우저를 열어서 localhost:8000/count 에 접속하면 현재까지 요청했던 요청 횟수가 나옵니다.
    server2.png
  • http handler
    • /count 요청은 counter 함수를 호출
    • 그 외의 요청은 handler 함수를 호출
    • 내부적으로 서버는 들어오는 각각의 요청 마다 별도의 고루틴을 실행해 동시에 여러 요청을 처리할 수 있게 합니다.
  • mutex
    • 여러 요청을 처리할 수 있지만, 두 개의 요청이 동시에 count를 갱신하려 할 경우 증가 값의 무결성을 보장하지 못할 수 있습니다.
    • 그러한 프로그램에서는 경쟁 조건(Race Condition)이라 불리는 심각한 버그가 발생할 수 있는데, 이것은 9.1절에서 자세히 다룹니다.
    • 이 문제를 방지하기 위해서 한 번에 하나의 고루틴만이 해당 변수에 접근하게 해야 하며, 이를 위해 mutex를 사용합니다.

웹 서버 구현 3

  • 예제를 더 풍부하게 하기 위해 핸들러 함수가 받는 요청의 헤더와 폼 데이터에 대해 보고해 서버가 요청을 조사하고 디버깅하기에 유용하게 만들었습니다.
// Server3은 디버깅하기 유용하게 요청의 헤더와 폼 데이터를 반환합니다.
package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", handler) // 각 요청은 핸들러를 호출합니다.
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

// handler는 HTTP 요청을 반환합니다.
func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto)
    for k, v := range r.Header {
        fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
    }
    fmt.Fprintf(w, "Host = %q\n", r.Host)
    fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr)
    if err := r.ParseForm(); err != nil {
        log.Print(err)
    }
    for k, v := range r.Form {
        fmt.Fprintf(w, "Form[%q] = %q\n", k, v)
    }
}

예제코드 [ch1/server3.go]

실행결과
$ go run ch1/server3.go

  • 브라우저를 열어서 localhost:8000 에 접속하면 http.Request 정보를 출력합니다.
    server3.png
  • 변수 범위
    • if 문의 조건 앞에 지역 변수 선언과 같은 간단한 구문을 나오게 할 수 있습니다.
    • line 23의 코드를 아래와 같이도 표현할 수 있지만, 문장을 결합해서 작성하는 것이 더 짧고 err 변수의 범위를 줄일 수 있어 더 좋은 방법입니다.
    err := r.ParseForm()
    if err != nil {
          log.Print(err)
      }
    
    • 범위에 대해서는 2.7절에서 더 자세히 다룹니다.

웹 서버 구현 4

  • 브라우저 상에서 lissajous GIF 애니메이션을 출력하게 합니다.
// Server4는 lissajous 애니메이션 GIF를 생성하여 http response로 반환합니다.
package main

import (
    "image"
    "image/color"
    "image/gif"
    "io"
    "log"
    "math"
    "math/rand"
    "net/http"
)

var palette = []color.Color{color.White, color.Black}

const (
    whiteIndex = 0 // 팔레트의 첫 번째 색상
    blackIndex = 1 // 팔레트의 다음 색상
)

func main() {
    handler := func(w http.ResponseWriter, r *http.Request) {
        lissajous(w)
    }
    http.HandleFunc("/", handler) // 각 요청은 핸들러를 호출합니다.
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

func lissajous(out io.Writer) {
    const (
        cycles  = 5     // x 진동자의 회전수
        res     = 0.001 // 회전각
        size    = 100   // 이미지 캔버스 크기 [-size..+size]
        nframes = 64    // 애니메이션 프레임 수
        delay   = 8     // 10ms 단위의 프레임 간 지연
    )
    freq := rand.Float64() * 3.0 // y 진동자의 상대적 진동수
    anim := gif.GIF{LoopCount: nframes}
    phase := 0.0 // 위상 차이
    for i := 0; i < nframes; i++ {
        rect := image.Rect(0, 0, 2*size+1, 2*size+1)
        img := image.NewPaletted(rect, palette)
        for t := 0.0; t < cycles*2*math.Pi; t += res {
            x := math.Sin(t)
            y := math.Sin(t*freq + phase)
            img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), blackIndex)
        }
        phase += 0.1
        anim.Delay = append(anim.Delay, delay)
        anim.Image = append(anim.Image, img)
    }
    gif.EncodeAll(out, &anim) // NOTE: 인코딩 오류 무시
}

예제코드 [ch1/server4.go]

실행결과
$ go run ch1/server4.go

  • 브라우저를 열어서 localhost:8000 에 접속하면 lissajous 애니메이션이 출력됩니다.
    server4.png
Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order: