[Golang] HTTP 웹 서버(Web Sever) 예제

Go 언어는 그 목적 중에 하나로 빠르고 효율적인 서버 어플리케이션 개발에 있는데요. 타 언어와는 다르게 Go는 웹서버 개발을 위한 별도의 외부 라이브러리의 도움 없이 기본 라이브러리만으로도 뛰어난 웹서버를 개발할 수 있습니다. Go 언어를 이용한 간단한 웹서버 예제를 정리해 둡니다.

이 웹서버는 2개의 URL 정의를 통해 1:1로 매칭되는 2가지 기능을 제공합니다. 예를들어서 URL이 /list로 끝나는 것과 /mean으로 구성되는 것인데요. 첫번째는 미리 정의해둔 단어와 그 의미들에 대한 목록을 서비스하고, 두번째는 QueryString으로 word 파라메터에 지정한 단어의 의미를 서비스하는데, 예를들어 http://localhost:8000/mean?word=GIS라고 하면 GIS의 의미를 문자열로 서비스합니다.

먼저 단어와 그 의미를 저정하는 map을 dic라는 타입으로 정의하고 앞서 언급한 2개의 URL 정의에 대한 서비스로 사용할 매서드를 dic 타입에 추가합니다. 아래 코드가 이에 대한 것입니다.

type dic map[string]string

func (msg dic) list(w http.ResponseWriter, req *http.Request) {
    for word, mean := range msg {
        fmt.Fprintf(w, "%s: %s\n", word, mean)
    }
}

func (msg dic) mean(w http.ResponseWriter, req *http.Request) {
    word := req.URL.Query().Get("word")
    mean, ok := msg[word]

    if !ok {
        w.WriteHeader(http.StatusNotFound)
        return
    }

    fmt.Fprintf(w, "%s\n", mean)
}

데이터 저장을 위한 저장소와 이 데이터를 서비스할 매서드 정의가 끝났으니, 이를 활용하는 코드가 필요한데요. 활용하는 코드라 함은 저장소에 몇가지 단어와 그 의미를 담고, 웹서버를 만들어 웹서버가 서비스할 URL을 정의하는 것입니다. 아래의 코드가 이에 대한 것입니다.

func main() {
    msg := dic{
        "GIS":   "지리정보시스템",
        "BIM":   "빌딩정보시스템",
        "CAD":   "컴퓨터를 이용한 디자인",
        "Dip2K": "Deeper Into Purity Since 2000",
    }

    http.HandleFunc("/list", msg.list)
    http.HandleFunc("/mean", msg.mean)

    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

이제 실행하고 웹브라우저에서 http://localhost:8000/list를 호출하면 다음과 같은 결과를 볼 수 있습니다.

GIS: 지리정보시스템
BIM: 빌딩정보시스템
CAD: 컴퓨터를 이용한 디자인
Dip2K: Deeper Into Purity Since 2000

그리고 http://localhost:8000/mean?word=GIS를 호출하면 다음과 같은 결과가 표시됩니다.

지리정보시스템

[Golang] 정렬(Sort)를 위한 sort.Interface 샘플 코드

Go에서 데이터 요소들을 가지는 슬라이스를 정렬하기 위해서는 sort 패키지의 Interface 인터페이스의 Len, Less, Swap 매서드를 구현해야 합니다. 아래의 코드는 문자열 데이터 요소를 가지는 슬라이스에 대한 정렬에 대한 sort.Interface의 구현 코드 및 활용 코드입니다.

package main

import (
    "fmt"
    "sort"
)

type StringSlice []string

func (p StringSlice) Len() int           { return len(p) }
func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p StringSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

func main() {
   names := []string{"이효숙", "김형준", "김종수", "김졍연", "김도현" }

    fmt.Println(names)

    sort.Sort(StringSlice(names))
    //sort.Strings(names)

    fmt.Println(names)
}

실행해 보면 다음과 같이 정렬되 결과를 볼 수 있습니다.

[이효숙 김형준 김종수 김졍연 김도현]
[김도현 김졍연 김종수 김형준 이효숙]

참고로 19번는 20번 코드로 대체가 가능한데요. 문자열 타입처럼 기본형 타입에 대한 데이터를 구성하는 슬라이스에 대한 정렬은 이미 sort 패키지에서 함수로 제공하고 있습니다.

[Golang] ring 자료구조

Ring 자료구조는 단어가 의미하듯이 Ring 자료를 구성하는 요소들을 순서대로 얻다가 마지막 요소에서 다시 처음 요소로 순회하면서 무한으로 계속 요소 데이터를 얻을 수 있는 자료구조입니다. Go에서 이러한 Ring 자료 구조에 대한 예제 코드는 아래와 같습니다.

package main

import (
    "container/ring"
    "fmt"
    "time"
)

func main() {
    dt := []string{"ONE", "TWO", "THREE", "FOUR", "FIVE"}

    r := ring.New(len(dt))
    for i := 0; i < r.Len(); i++ {
        r.Value = dt[i]
        r = r.Next()
    }

    r.Do(func(x interface{}) {
        fmt.Println(x)
    })

    fmt.Println("------")

    for _ = range time.Tick(time.Second * 1) {
        fmt.Println(r.Value)
        r = r.Next()
    }
}

링의 자료구조 객체는 12번 코드처럼 ring.New 함수를 통해 생성할 수 있으며 이 함수의 인자는 링을 구성하는 데이터 개수입니다. 10번 코드에서 링의 데이터 요소로 구성할 슬라이스 객체를 준비해 두었고 이 슬라이스의 갯수를 인자로 ring.New 함수를 호출하고 있습니다. 13번의 for 문을 통해 슬라이스의 데이터를 하나 하나 링에 추가합니다. 18번 코드에서 링의 Do 함수는 각 요소에 대해 실행할 함수를 지정해 두면 링을 구성하는 모든 데이터 각각에 대해 실행할 함수를 호출해 줍니다. 여기서는 단순히 화면 출력에 대한 코드를 함수로 지정하고 있습니다. 24번 코드가 바로 1초 간격으로 무한 반복으로 링을 구성하는 요소를 얻는 for 문입니다. 위의 코드를 실행하면 다음과 같은 결과를 얻을 수 있습니다.

ONE
TWO
THREE
FOUR
FIVE
------
ONE
TWO
THREE
FOUR
FIVE
ONE
TWO
THREE
FOUR
FIVE
ONE

.
.
.

[Golang] Heap 자료구조

go에서 패키지 라이브러리로 제공하는 힙 자료구조가 있습니다. 제공되는 힙 자료구조는 부모 노드의 값은 자식 노드의 값보다 반드시 작거나 같다라는 조건을 충족해야 합니다. 이러한 조건 충족을 비교 매서드와 힙 자료구조에 요소를 추가(Push)하고 가져오는(Pop) 매서드 등을 제공하는 heap.Interface 인터페이스를 직접 구현한 Custom Type 소스 코드를 작성해야 합니다.

만약 정수형 값을 요소로 하는 힙 자료구조를 만든다고 할때 heap.Interface 인터페이스를 구현하는 Custom Type은 다음과 같습니다.

type IntHeap []int

func (h IntHeap) Len() int {
    return len(h)
}

func (h IntHeap) Less(i, j int) bool {
    return h[i] < h[j]
}

func (h IntHeap) Swap(i, j int) {
    h[i], h[j] = h[j], h[i]
}

func (h *IntHeap) Push(elem interface{}) {
    *h = append(*h, elem.(int))
}

func (h *IntHeap) Pop() interface{} {
    old := *h
    n := len(old)
    elem := old[n-1]
    *h = old[0 : n-1]

    return elem
}

heap.Interface에서 구현해야 하는 매서드는 아래와 같습니다.

매서드 설명
func (h IntHeap) Less(i, j int) bool i와 j 번째 요소의 값을 비교하여 i번째 요소가 j번째 요소보다 작으면 true을 반환
func (h IntHeap) Len() int 요소의 개수를 반환
func (h IntHeap) Swap(i, j int) i번째와 j번째 요소의 값을 서로 바꿈
func (h *IntHeap) Push(elem interface{}) 요소의 값을 추가
func (h *IntHeap) Pop() interface{} 요소의 값을 가져옴(작은 값 순서로 값이 얻어짐)

이제 위의 IntHeap을 사용하는 소스 코드를 살펴 보면 아래와 같습니다.

// main
package main

import (
    "container/heap"
    "fmt"
)

type IntHeap []int

func (h IntHeap) Len() int {
    return len(h)
}

func (h IntHeap) Less(i, j int) bool {
    return h[i] < h[j] 
} 

func (h IntHeap) Swap(i, j int) { 
    h[i], h[j] = h[j], h[i] 
} 

func (h *IntHeap) Push(elem interface{}) { 
    *h = append(*h, elem.(int)) 
} 

func (h *IntHeap) Pop() interface{} { 
    old := *h 
    n := len(old) 
    elem := old[n-1] 
    *h = old[0 : n-1] 

    return elem 
} 

func main() { 
    h := &IntHeap{9, 6, 4, 1} 
    heap.Init(h) 

    fmt.Println(*h) 
    fmt.Println("---------------") 

    heap.Push(h, 7) 
    heap.Push(h, 9) 
    heap.Push(h, 11) 

    for h.Len() > 0 {
        m := heap.Pop(h)
        fmt.Print(m, " ")
    }
}

앞서 설명한 코드에 대한 설명은 하지 않고 main 함수를 살펴보면.. 먼저 37번 코드에서 힙 데이터로 구성할 요소들에 대한 슬라이스 객체를 생성합니다. 그리고 38번에서 이 슬라이스 객체를 통해 heap 객체로 초기화합니다. 그리고 43번~45번 코드에서 다시 몇개의 데이터를 더 추가하고 47번의 for 문을 통해 힙의 요소를 순서대로 출력해보면 작은 값에서 점점 큰 값이 얻어지는 것을 볼 수 있습니다. 위의 코드에 대한 실행 결과는 아래와 같습니다.

[1 6 4 9]
---------------
1 4 6 7 9 9 11

이 글은 예제로 배우는 GO 프로그래밍에서 학습한 코드를 제가 이해한 내용을 덧붙여 정리한 글입니다.