https://github.com/cloud-native-go/examples 로 공부한 내용입니다.

 

1. net/http를 이용한 Rest API 

package main

import (
	"log"
	"net/http"
)

func helloGoHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello net/http!\n"))
}

func main() {
	http.HandleFunc("/", helloGoHandler)


	// 2번째 인자는 multiplexor. nil인 경우 DefaultServeMux 이용
	//ListenAndServe는 에러가 발생한 경우에만 반환.
    //log.Fatal은 에러가 발생할 경우, 에러메시지 반환하고 종료
	log.Fatal(http.ListenAndServe(":8080", nil))

}

 

multiplexor로 패턴(경로 등)이랑 함수를 매핑해줍니다. 

 

pattern 매칭

이러한 코드가 있고,

 

이런 식으로 등록을 하게 됩니다.

 

 

하지만 다른 mux를 사용하고 싶을 수 있습니다.

책에서는 gorllia/mux를 사용하는 예제를 설명합니다.

 

2. gorilla mux를 이용한 rest api

package main

import (
	"log"
	"net/http"
	//따로 go init {...} 해서 go.mod 만들어야 함.
	"github.com/gorilla/mux"
)

func helloGoHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello net/http!\n"))
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/", helloGoHandler)

	log.Fatal(http.ListenAndServe(":8080", r))

}

 

 

앞에서 사용한 net/http에서 추가된 기능으로는

경로에 /{key} 같은 형태를 넣을 수 있고 정규표현식도 사용할 수 있습니다.

 

또한 이러한 파라미터를 추출할 수 있습니다.

 

이를 적용한 코드가 아래 코드입니다.

package main

import (
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

func helloGoHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	var name string = vars["key"]

	w.Write([]byte("Hello " + name + "\n"))
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/{key}", helloGoHandler)

	log.Fatal(http.ListenAndServe(":8080", r))

}

 

 

실제 출력

보시면 me를 출력한 것을 볼 수 있습니다.

쉽게 파라미터를 추출할 수 있는 것을 확인할 수 있습니다.

 

 

이를 이용해서 put과 get 기능을 만들어보겠습니다.

package main

import "errors"

//멀티 쓰레드로 동작하면?
var store = make(map[string]string)

var ErrorNoSuchKey = errors.New("no such key")

func Delete(key string) error {
	delete(store, key)

	return nil
}

func Get(key string) (string, error) {
	value, ok := store[key]

	if !ok {
		return "", ErrorNoSuchKey
	}

	return value, nil
}

func Put(key string, value string) error {
	store[key] = value

	return nil
}

 

package main

import (
	"errors"
	"io"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

func keyValuePutHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	key := vars["key"]

	value, err := io.ReadAll(r.Body)
	defer r.Body.Close()

	if err != nil {
		http.Error(w,
			err.Error(),
			http.StatusInternalServerError)
		return
	}

	err = Put(key, string(value))

	if err != nil {
		http.Error(w,
			err.Error(),
			http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusCreated)

	log.Printf("PUT key=%s value=%s\n", key, string(value))
}

func keyValueGetHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	key := vars["key"]

	value, err := Get(key)
	if errors.Is(err, ErrorNoSuchKey) {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Write([]byte(value))

	log.Printf("GET key=%s\n", key)
}

func keyValueDeleteHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	key := vars["key"]

	err := Delete(key)

	if err != nil {
		http.Error(w,
			err.Error(),
			http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)

	log.Printf("Delete key=%s\n", key)

}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/v1/{key}", keyValuePutHandler).Methods("PUT")
	r.HandleFunc("/v1/{key}", keyValueGetHandler).Methods("GET")
	r.HandleFunc("/v1/{key}", keyValueDeleteHandler).Methods("DELETE")

	log.Fatal(http.ListenAndServe(":8080", r))
}

 

테스트를 해보면,

1. PUT

key : a, value : hello

 

2. GET

value인 hello 출력되는 것 확인

3.DELETE

key가 a인 것 삭제

 

4. 삭제 후, GET

no such key 출력 확인

잘 동작하는 것을 확인할 수 있습니다.

 

 

3. 동시성 문제

하지만 앞에서 봤던 코드에서 멀티 쓰레드로 동작할 경우 동시성 문제가 생길 수 있습니다.

package main

import "errors"

//멀티 쓰레드로 동작하면?
var store = make(map[string]string)

var ErrorNoSuchKey = errors.New("no such key")

func Delete(key string) error {
	delete(store, key)

	return nil
}

func Get(key string) (string, error) {
	value, ok := store[key]

	if !ok {
		return "", ErrorNoSuchKey
	}

	return value, nil
}

func Put(key string, value string) error {
	store[key] = value

	return nil
}

key-value 저장소를 그냥 map으로 이용했기 때문입니다.

 

 

이를 해결하기 위해서 mutex를 이용합니다.

 

 

package main

import (
	"errors"
	"sync"
)

var store = struct {
	sync.RWMutex
	m map[string]string
}{m: make(map[string]string)}

var ErrorNoSuchKey = errors.New("no such key")

func Delete(key string) error {
	store.Lock()
	delete(store.m, key)
	store.Unlock()

	return nil
}

func Get(key string) (string, error) {
	store.RLock()
	value, ok := store.m[key]
	store.RUnlock()

	if !ok {
		return "", ErrorNoSuchKey
	}

	return value, nil
}

func Put(key string, value string) error {
	store.Lock()
	store.m[key] = value
	store.Unlock()

	return nil
}

 

코드를 보면, get은 read lock을 사용하는 것을 볼 수 있습니다.

 

반응형

+ Recent posts