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

in #kr-dev6 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

Coin Marketplace

STEEM 0.15
TRX 0.16
JST 0.028
BTC 68650.88
ETH 2429.74
USDT 1.00
SBD 2.37