web framework 역할과 종류 에서 Go에서 star별로 조사를 했다.
많은 WebFramework가 있지만 Gin이 star가 가장많기 때문에 선택을 해본다.

gin github
gin-gonic 에 들어가서 다운로드 받고, 배울수 있다.

Gin?

Gin은 golang으로 작성된 HTTP web framework를 말하고, 성능이 빠르다는 장점이 있다.
Logger, Authorization, GZIP, DB와 연동해서 사용할 수 있다.
뭐 다 좋다고 하겠으니 일단 사용해보기로!

어떻게 시작?

무조건 개발은 해보면서 습득하는게 좋으니 gin examples가 있는곳을 살펴보기로한다. Gin으로 작성된 Awesome Project의 리스트도 확인이 가능하다.
Awesome Project에 보면 TF와 함께 개인 사진 저장소를 구현한 페이지 photoprism가 있는데 완성도가 훌륭하다. 데모페이지를 들어가보면 정말 이런걸 만들고 싶다라는 생각이 드는 페이지가 보인다.

Quick Start

quick start를 하고 나면,

  • go get -u github.com/gin-gonic/gin
  • go get github.com/kardianos/govendor

설치를 govendor가 없다고 나온다. command not found
그 이유는 govendor의 환경변수를 지정하지 않아서 생기는 문제~ GOPATHGOBIN의 경로를 ~/.bashrc에 지정해주자

export GOPATH=~/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN

다음과 같이 ~/.bashrc에 입력했다면 source ~/.bashrc를 입력해서 적용하면
govendor를 입력할 수 있을것이다.

예제 코드를 입력하고 http://localhost:8080/ping 실행하면 pong의 응답을 받는 예제이다.

Examples

git clone https://github.com/gin-gonic/examples.git의 소스코드를 내려받고,
https://gin-gonic.com/docs/examples/에서 예제에 대한 설명이 확인 가능하다.
다음에는 예제에서 각각 어떤 내용을 배우는지에 대한 정리를 해볼까한다.

Go Framework는 굉장히 다양하다.
net/http 패키지를 사용해서 구현해도 좋지만,
복잡하고 다양한 기능을 간편하게 구현하기 위해서는 Web Framework를 사용하는게 좋다.
awesome-go.com을 보면 전부 정리가 되어있다...
아주 훌륭한 사람들

Web Framework의 역할과 선택기준

Web Framework는 Request, Handler를 맵핑하는 Routing, 데이터 바인딩, 컨텍스트 기능 등 다양하게 제공하고 있어 우리가 별도의 개발 리소스 없이 간편하게 구현이 가능하도록 제공하고 있다. 그런데 문제는 너무 많은 웹프레임워크가 있다는점!!! 항상 선택을 고민하다 개발을 하기도 전에 개발이 끝나버린다. 프레임워크를 선택하는 기준은 내가 하고자 하는 기능이 충분하게 이미 구현되어 있는지 확인하면 된다. 아마도 제일 많은 star를 받은 repo의 framework를 선택하는게 안전빵이 아닌가싶다. (나는 잘 모르니까..)

Go 언어의 Web Framework

위처럼 다양한 웹프레임워크를 제공하고 있다. 2019.08.31을 기준으로 (star)를 적어놓았다. 이중에 무엇을 선택할지 정말 많은 Web Framework가 있구나... 이중에 Web Framework를 어떤것을 사용해야 할지!!!! 또 찾다보니.. (역시 찾기만하는)

Go-Web-Framework-Stars 이미 정리해놓은게 역시 있었다. 여기를 살펴보면 유명한순으로 나열되어 있는데, 아래 표시한것보다 많다.

몇가지 글을 참고하면, 최근에는 Echo(python의 flask와 유사하다. 사촌같다. 등등), gorillia/mux, chia, Alice가 최고다... gin, gobuffalo가 최고다라고 말하고 있는데, 사실 뭐.. 내가 쓰기 편한게 최고지 한번씩 그래도 코드를 보는것도 도움이 될것 같다. 다음편에는 한번 선정해서 코드도 보고 어떻게 사용하는지 살펴볼 생각이다. reddit

만약 당신이 사용하고 있다면, 당신은 어떤 Web Framework가 가장 좋았는가?
나에게 추천을 부탁한다.

~61까지 살펴보았고 이전 내용 살펴보기
이제는 62부터 살펴보자! Go 언어에서 동시성은 중요한 기능이라고 설명했는데, 어떤 기능이 있을까?

동시성

나는 이미 Concurrency에 대해서 이해를 하고 있는데,
조금더 디테일하게 알고싶으면 golang codewalk 를 참고

Goroutines

_고루틴은 Go 런타임에 의해 관리되는 경량 쓰레드이다.

go f(x, y, z) 의 코드는 새로운 고루틴을 시작시킨다.

현재의 고루틴에서는 f, x, y, z가 평가(evaluation) 되고,
새로운 고루틴 f가 수행(exeuction) 된다.

고루틴은 동일한 주소 공간에서 실행되므로, 공유되는 자원으로의 접근은 반드시 동기화가 되어야 한다.
golang sync에서 유용한 기본 기능을 제공하고 있다.

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

위 결과를 실행하면 hello, world가 번갈아가면서 수행이 된다.

Channels

채널은 채널 연산자 <-를 이용해 값을 주고 받을 수 있는 타입이 존재하는 파이프이다.
(데이터는 화살표 방향으로 흐른다.)

ch <- v // v를 ch로 보낸다.
v := <- ch // ch로부터 값을 받아서 v로 넘긴다. 

맵이나 슬라이스처럼 채널은 사용되기 전에 생성되어야 한다.

ch := make(chan int)

기본적으로 송/수신은 상대편이 준비될 때까지 블록된다.
이런 특성이 고루틴이 명시적인 락이나 조건 없이 동기화 될 수 있도록 돕는다.

아래 예제에서는 sumc로 보내고, c로 부터 받은 값을 x,y에 넘겨준다.

package main

import "fmt"

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c <- sum // send sum to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := <-c, <-c // receive from c

    fmt.Println(x, y, x+y)
}

동시성을 지원하기 때문에 채널이 필요한건가...
위에 예제에서는 a라는 리스트의 모든 값을 더하는 과정인데,
두개의 고루틴을 이용해서 합을하고, 마지막에 두 값을 합치는 예제이다.
각각의 스레드에서의 결과는 c에 저장되고 종료되면 x, y로 가니 최종적으로 x+y를 계산
일단 사용성만 이해하는걸로라고 하고 넘어가려고 하니

65에서 버퍼링되는 채널의 주제가 나왔다.
채널이 버퍼링 될수가 있다고 하는데,

ch := make(chan int, 100) 의 용량을 지정할 수 있는데,
용량만큼 버퍼링되는 채널을 생성할 수 있다.
버퍼링되는 채널로의 송신은 버퍼가 꽉 찰 때까지 블록된다.
수신측은 버퍼가 빌 때 블록이된다.
언제 어떻게 써야하는지는 모르겠음 일단 버퍼 용량을 넣을 수 있다는 정도로 이해
웹에서 UI 업데이트를 동시에 하기 위해서 사용하려나 모르겠음.

Range와 Close

데이터 송신측은 더이상 보낼 값이 없다는 것을 알리기 위해 채널을 close할 수 있다.
수신측에서는 v, ok := <-ch 다음과 같이 두번째 인자를 줌으로써 채널이 닫혔는지 테스트 가능
채널이 이미 닫혔으면 false

주의 송신측만 채널을 닫을 수 있다. 수신측에서는 불가능 (이미 닫힌 채널에 데이터를 보내면 응?)
채널은 파일과 다르다. 항상 닫을 필요가 없다. range의 루프를 종료시키는 느낌과 유사함
더이상 값을 보낼게 없다고 말할때

package main

import (
    "fmt"
)

func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c) // channel을 닫음
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

Select

고루틴이 다수의 통신 동작으로부터 수행 준비를 기다릴 수 있게 한다.
selectcase구문으로 받은 통신 동작들 중 하나가 수행될 수 있을때까지 수행을 블록
다수의 채널이 동시에 준비되면 그 중 하나를 무작위로 선택

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

select, case를 같이 쓰는 예제인것 같은데
이것도 어디에 어떻게 쓰일지... 감이 안오는데
여러가지 조건이 있을때 특정 값을 업데이트를 할때 사용?
웹에서 이점이 있을것 같은데.. 일단 이렇게 쓰이는구나... 정도만 이해

switch와 동일하게 case에서 default를 사용하게 되면
블로킹 없이(비동기적인) 송/수신을 하고자 할 때 default를 사용하면 된다.

select {
case i := <-c:
    // i를 사용
default:
    // c로부터의 수신은 블록된 상태
}

뒤에 연습문제도 있지만 집중력이 떨어짐
이후에 더 살펴보기 위해서는 레퍼런스, 튜토리얼, 비디오 자료갸 있는 Go 문서를 참고

Go 언어를 배우기 위해서 1부터 ~52까지 진행한 내용을 정리했었는데,
53부터 다시 시작을 해보려고 한다!

Interface

인터페이스는 메소드의 집합으로 정의된다.
메소드들의 구현되어 있는 타입의 값은 모두 인터페이스의 값이 될 수 있다.
이것은... 도대체 언제 쓰는것일까.. (나중에 생각해보기로... 일단 모르겠음)

package main

import (
    "fmt"
    "math"
)

type Abser interface {
    Abs() float64
}

func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}

    a = f  // a MyFloat implements Abser
    a = &v // a *Vertex implements Abser
    a = v  // a Vertex, does NOT
    // implement Abser

    fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

인터페이스의 메소드들을 구현하면 인터페이스를 구현하게 됩니다?
이를 위해 명시적으로 선언할 게 없습니다.
암시적 인터페이스는 인터페이스를 정의한 패키지로부터 구현 패키지를 분리 (decouple)해 줍니다.
다른 의존성 또한 없음은 물론이다? 이게 무슨 번역기 돌려놔서 무슨말인지 모르겠네...
일단 나중에 필요하면 찾아보는 걸로 #54

Error

나의 메소드 Error로 구성된 내장 인터페이스 타입 Error에서 나왔다.

type error interface {
    Error() string
}

package main

import (
    "fmt"
    "time"
)

type MyError struct {
    When time.Time
    What string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("at %v, %s",
        e.When, e.What)
}

func run() error {
    return &MyError{
        time.Now(),
        "it didn't work",
    }
}

func main() {
    if err := run(); err != nil {
        fmt.Println(err)
    }
}

집중력이 매우 흐려지고 있다.
점점 상황이 되면 다시 찾아보자... 하는 마음이 생기기 시작한다.
집중하도록 한다.

웹 서버

드디어 나왔다. node.js를 할지 Go를 해볼지 고민했었던
웹 서버! 어떻게 동작할까?

package main

import (
    "fmt"
    "net/http"
)

type Hello struct{}

func (h Hello) ServeHTTP(
    w http.ResponseWriter,
    r *http.Request) {
    fmt.Fprint(w, "Hello!")
}

func main() {
    var h Hello
    http.ListenAndServe("localhost:4000", h)
}

http.Handler를 구현한 어떤 값을 사용하여 HTTP 요청(requests)을 제공한다.
위의 예제에서는 Hello라는 타입은 http.Handler를 구현한다.

파일을 생성하고 실행해보려고 하니... Go는 어떻게 파일을 만들고 실행하는지도 모른다.
중간에 뜬금없이만 Go를 실행하는 방법은 아래 설명

Go 실행하기 (뜬금없지만)

실행하는 방법 이 설치하는 페이지에 설명이 되어 있다.

$ cd $HOME/go/src/hello
$ go build

$ ./hello
hello, world

hello 안에는 *.go의 파일이 있어야 한다.
build하고 나면 마지막 디렉토리명으로 실행 파일이 생성된다.

HTTP Handler (HTTP 핸들러) 연습

#58 에서 HTTP 핸들러 연습을 하는데,
아니 배운게 없는데 갑자기 핸들러를 등록하라니.. 도전해보도록 한다.

package main

import (
    "net/http"
)

func main() {
    // your http.Handle calls here
    http.ListenAndServe("localhost:4000", nil)    
}

정답은 바로!

package main

import (
    "fmt"
    "net/http"
)

type String string

func (s String) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, s)
}

type Struct struct {
    Greeting string
    Punct string
    Who string
}

func (s *Struct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, s.Greeting, s.Punct, s.Who)
}


func main() {
    http.Handle("/string", String("I'm a frayed knot."))
    http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
    http.ListenAndServe("localhost:4000", nil)
}

정답은 이미 풀어놓은 사람의 소스를 가져왔다. 쉽다는데 아직은 어렵다.
이렇게 풀어서 코딩하는구나~ 라는 정도로만 이해하고 스킵! 받아들여~

Image

Pakcage Image는 Image 인터페이스를 정의한다.
자세한 내용은 다음을 참고하자
지금은 맛보기!

package main

import (
    "fmt"
    "image"
)

func main() {
    m := image.NewRGBA(image.Rect(0, 0, 100, 100))
    fmt.Println(m.Bounds())
    fmt.Println(m.At(0, 0).RGBA())
}

// 출력결과
(0,0)-(100,100)
0 0 0 0

속도가 점점 느려지고 있는데,
어떻게 한번에 이해하겠나?
다음은 Go 언어에서 동시성에 대한 내용이다.
잠시 쉬었다가 고고씽

+ Recent posts