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로 패턴(경로 등)이랑 함수를 매핑해줍니다.
이러한 코드가 있고,
이런 식으로 등록을 하게 됩니다.
하지만 다른 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
2. GET
3.DELETE
4. 삭제 후, GET
잘 동작하는 것을 확인할 수 있습니다.
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을 사용하는 것을 볼 수 있습니다.
반응형
'BackEnd > go' 카테고리의 다른 글
[Go] 리소스 상태 유지 (0) | 2024.07.09 |
---|---|
[Go] 동시성 패턴 future (0) | 2024.06.29 |
[Go] struct 와 포인터(자바 클래스와 비교) (0) | 2024.06.25 |
[GO] go를 이용한 안정성 패턴 구현(서킷) (0) | 2024.06.12 |