Ver3.0 ๐ Go์ธ์ด ๋ฏธ๋ค์จ์ด์ HTTP ํธ๋ค๋ฌ ์ฒด์ธ์ผ๋ก ๋ง๋๋ ์์ฒญ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ ์๋ฒฝ ๊ฐ์ด๋

๐ Go์ธ์ด ๋ฏธ๋ค์จ์ด์ HTTP ํธ๋ค๋ฌ ์ฒด์ธ์ผ๋ก ๋ง๋๋ ์์ฒญ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ ์๋ฒฝ ๊ฐ์ด๋
์น ์๋ฒ์ ์จ์ ์์ , ๋ฏธ๋ค์จ์ด์ ๋ชจ๋ ๊ฒ์ ํํค์ณ๋ณด์!
๐ฏ ์น ๊ฐ๋ฐ์ ํ๋ค ๋ณด๋ฉด ๋ก๊น , ์ธ์ฆ, ์๋ฌ ์ฒ๋ฆฌ ๊ฐ์ ๊ณตํต ๊ธฐ๋ฅ๋ค์ ๋งค๋ฒ ๋ฐ๋ณตํด์ ์์ฑํ๊ฒ ๋์ง? ์ด๋ฐ ๋ฐ๋ณต ์์ ์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ ๋ฐ๋ก ๋ฏธ๋ค์จ์ด์ผ. Go ์ธ์ด์์ ๋ฏธ๋ค์จ์ด๋ HTTP ์์ฒญ์ด ์ต์ข ํธ๋ค๋ฌ์ ๋๋ฌํ๊ธฐ ์ ์ ๊ฑฐ์น๋ ์ผ๋ จ์ ์ฒ๋ฆฌ ๋จ๊ณ๋ฅผ ์๋ฏธํด.
์ค๋์ Go ์ธ์ด๋ก ๋ฏธ๋ค์จ์ด๋ฅผ ๊ตฌํํ๊ณ , ์ฌ๋ฌ ๋ฏธ๋ค์จ์ด๋ฅผ ์ฒด์ธ์ฒ๋ผ ์ฐ๊ฒฐํด์ ๊ฐ๋ ฅํ ์์ฒญ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์น๊ตฌ์๊ฒ ์ค๋ช ํ๋ฏ ํธํ๊ฒ ์๋ ค์ค๊ฒ! ๐
๐ ๋ฏธ๋ค์จ์ด๊ฐ ๋ญ๋ฐ? ์ ํ์ํ ๊ฑฐ์ผ?
๋ฏธ๋ค์จ์ด๋ฅผ ์ดํดํ๋ ค๋ฉด ๋จผ์ ์น ์๋ฒ์์ ์์ฒญ์ด ์ด๋ป๊ฒ ์ฒ๋ฆฌ๋๋์ง ์์์ผ ํด. ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๋ฉด, ๊ทธ ์์ฒญ์ ์ฌ๋ฌ ๋จ๊ณ๋ฅผ ๊ฑฐ์ณ์ ์ต์ข ์ ์ผ๋ก ์๋ต์ ์์ฑํ๊ฒ ๋ผ.
์๋ฅผ ๋ค์ด๋ณผ๊น? ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ์ด ํ์ํ ํ์ด์ง์ ์ ๊ทผํ๋ค๊ณ ์๊ฐํด๋ด:
1๏ธโฃ ๋จผ์ ์์ฒญ์ด ๋ค์ด์ค๋ฉด ๋ก๊ทธ๋ฅผ ๋จ๊ฒจ์ผ ํ๊ณ
2๏ธโฃ ์ฌ์ฉ์๊ฐ ์ธ์ฆ๋์๋์ง ํ์ธํด์ผ ํ๊ณ
3๏ธโฃ ๊ถํ์ด ์๋์ง ์ฒดํฌํด์ผ ํ๊ณ
4๏ธโฃ ์ค์ ๋น์ฆ๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ๊ณ
5๏ธโฃ ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ์ ์ ํ ์ฒ๋ฆฌํด์ผ ํด
๐ญ ๋ฏธ๋ค์จ์ด๋ ์ด๋ฐ ๊ณตํต ์ฒ๋ฆฌ ๋ก์ง์ ๋ ๋ฆฝ์ ์ธ ์ปดํฌ๋ํธ๋ก ๋ถ๋ฆฌํ ๊ฑฐ์ผ. ๊ฐ ๋ฏธ๋ค์จ์ด๋ ํ๋์ ์ฑ ์๋ง ๊ฐ์ง๊ณ , ์์ฒญ์ ์ฒ๋ฆฌํ ํ ๋ค์ ๋ฏธ๋ค์จ์ด๋ ์ต์ข ํธ๋ค๋ฌ๋ก ์ ๋ฌํ๋ ๊ตฌ์กฐ์ง.
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์๋ ์ฌ์ฉ์ ์์ฒญ์ ์ฒ๋ฆฌํ ๋ ์ด๋ฐ ๋ฏธ๋ค์จ์ด ํจํด์ ํ์ฉํ๋ฉด ์ฝ๋์ ์ฌ์ฌ์ฉ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ด ์์ฒญ๋๊ฒ ์ข์์ ธ! ๐ช
๐ ๏ธ Go์์ ๋ฏธ๋ค์จ์ด์ ๊ธฐ๋ณธ ๊ตฌ์กฐ
Go ์ธ์ด์์ ๋ฏธ๋ค์จ์ด๋ ์ฌ์ค ํจ์๋ฅผ ๋ฐํํ๋ ํจ์์ผ. ์ข ํท๊ฐ๋ฆด ์ ์๋๋ฐ, ์ฒ์ฒํ ์ดํด๋ณด์!
Go์ HTTP ํธ๋ค๋ฌ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ด๋ฐ ์๊ทธ๋์ฒ๋ฅผ ๊ฐ์ ธ:
func(w http.ResponseWriter, r *http.Request)
๋ฏธ๋ค์จ์ด๋ ์ด๋ฐ ํธ๋ค๋ฌ๋ฅผ ๋ฐ์์, ์ถ๊ฐ ๊ธฐ๋ฅ์ ๋ํ ์๋ก์ด ํธ๋ค๋ฌ๋ฅผ ๋ฐํํ๋ ๊ฑฐ์ง. ํจํด์ ์ด๋ ๊ฒ ์๊ฒผ์ด:
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ์ ์ฒ๋ฆฌ ์์
fmt.Println("๋ฏธ๋ค์จ์ด ์์")
// ๋ค์ ํธ๋ค๋ฌ ํธ์ถ
next.ServeHTTP(w, r)
// ํ์ฒ๋ฆฌ ์์
fmt.Println("๋ฏธ๋ค์จ์ด ์ข
๋ฃ")
})
}
๐ก ํต์ฌ ํฌ์ธํธ!
๋ฏธ๋ค์จ์ด๋ next.ServeHTTP(w, r)๋ฅผ ํธ์ถํด์ ๋ค์ ๋จ๊ณ๋ก ์์ฒญ์ ์ ๋ฌํด. ์ด ํธ์ถ ์ ํ๋ก ์ํ๋ ์์ ์ ์ถ๊ฐํ ์ ์์ด. ๋ง์น ์ํ ๊ป์ง์ฒ๋ผ ์์ฒญ์ ๊ฐ์ธ๋ ๊ฑฐ์ง! ๐ง
์ค์ ์์ : ๋ก๊น ๋ฏธ๋ค์จ์ด ๋ง๋ค๊ธฐ
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๋ก๊น ๋ฏธ๋ค์จ์ด๋ฅผ ๋ง๋ค์ด๋ณผ๊น? ๋ชจ๋ HTTP ์์ฒญ์ ๋ฉ์๋, ๊ฒฝ๋ก, ์ฒ๋ฆฌ ์๊ฐ์ ๊ธฐ๋กํ๋ ๋ฏธ๋ค์จ์ด์ผ:
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// ์์ฒญ ์ ๋ณด ๋ก๊น
log.Printf("์์ฒญ ์์: %s %s", r.Method, r.URL.Path)
// ๋ค์ ํธ๋ค๋ฌ ์คํ
next.ServeHTTP(w, r)
// ์ฒ๋ฆฌ ์๊ฐ ๋ก๊น
duration := time.Since(start)
log.Printf("์์ฒญ ์๋ฃ: %s %s (์์์๊ฐ: %v)",
r.Method, r.URL.Path, duration)
})
}
func HelloHandler(w http.ResponseWriter, r *http.Request) {
time.Sleep(100 * time.Millisecond) // ์์
์๋ฎฌ๋ ์ด์
fmt.Fprintf(w, "์๋
ํ์ธ์!")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", HelloHandler)
// ๋ฏธ๋ค์จ์ด ์ ์ฉ
wrappedMux := LoggingMiddleware(mux)
log.Println("์๋ฒ ์์: http://localhost:8080")
http.ListenAndServe(":8080", wrappedMux)
}
์ด ์ฝ๋๋ฅผ ์คํํ๊ณ http://localhost:8080/hello์ ์ ์ํ๋ฉด, ์ฝ์์ ์ด๋ฐ ๋ก๊ทธ๊ฐ ์ฐํ:
2024/01/15 10:30:45 ์์ฒญ ์์: GET /hello
2024/01/15 10:30:45 ์์ฒญ ์๋ฃ: GET /hello (์์์๊ฐ: 100.5ms)
๊ฐ๋จํ์ง? ๐ ์ด์ ์ด ํจํด์ ํ์ฅํด์ ๋ ๋ณต์กํ ๋ฏธ๋ค์จ์ด๋ฅผ ๋ง๋ค ์ ์์ด!
๐ ๋ฏธ๋ค์จ์ด ์ฒด์ด๋: ์ฌ๋ฌ ๋ฏธ๋ค์จ์ด ์ฐ๊ฒฐํ๊ธฐ
์ค์ ํ๋ก์ ํธ์์๋ ์ฌ๋ฌ ๋ฏธ๋ค์จ์ด๋ฅผ ์กฐํฉํด์ ์ฌ์ฉํ๊ฒ ๋ผ. ๋ก๊น ๋ ํ๊ณ , ์ธ์ฆ๋ ํ๊ณ , CORS๋ ์ฒ๋ฆฌํ๊ณ ... ์ด๋ฐ ๋ฏธ๋ค์จ์ด๋ค์ ์ด๋ป๊ฒ ๊น๋ํ๊ฒ ์ฐ๊ฒฐํ ์ ์์๊น?
๋ฐฉ๋ฒ 1: ์๋ ์ฒด์ด๋
๊ฐ์ฅ ์ง๊ด์ ์ธ ๋ฐฉ๋ฒ์ ๋ฏธ๋ค์จ์ด๋ฅผ ํ๋์ฉ ๊ฐ์ธ๋ ๊ฑฐ์ผ:
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/data", DataHandler)
// ๋ฏธ๋ค์จ์ด๋ฅผ ๊ฒน๊ฒน์ด ๊ฐ์ธ๊ธฐ
handler := LoggingMiddleware(
AuthMiddleware(
CORSMiddleware(mux),
),
)
http.ListenAndServe(":8080", handler)
}
์ด ๋ฐฉ์์ ๊ฐ๋จํ์ง๋ง, ๋ฏธ๋ค์จ์ด๊ฐ ๋ง์์ง๋ฉด ์ฝ๋๊ฐ ๋ณต์กํด์ ธ. ๊ดํธ๊ฐ ๋๋ฌด ๋ง์์ ๊ฐ๋ ์ฑ๋ ๋จ์ด์ง๊ณ ! ๐
๋ฐฉ๋ฒ 2: ์ฒด์ธ ํฌํผ ํจ์ ๋ง๋ค๊ธฐ
๋ ๊น๋ํ ๋ฐฉ๋ฒ์ ์ฒด์ธ์ ๋ง๋ค์ด์ฃผ๋ ํฌํผ ํจ์๋ฅผ ์์ฑํ๋ ๊ฑฐ์ผ:
type Middleware func(http.Handler) http.Handler
// ์ฌ๋ฌ ๋ฏธ๋ค์จ์ด๋ฅผ ์ฒด์ธ์ผ๋ก ์ฐ๊ฒฐ
func Chain(middlewares ...Middleware) Middleware {
return func(final http.Handler) http.Handler {
// ์ญ์์ผ๋ก ์ ์ฉ (๋ง์ง๋ง๋ถํฐ ์ฒซ๋ฒ์งธ๊น์ง)
for i := len(middlewares) - 1; i >= 0; i-- {
final = middlewares[i](final)
}
return final
}
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/data", DataHandler)
// ์ฒด์ธ ํจ์๋ก ๊น๋ํ๊ฒ ์ฐ๊ฒฐ
chain := Chain(
LoggingMiddleware,
AuthMiddleware,
CORSMiddleware,
)
http.ListenAndServe(":8080", chain(mux))
}
โจ ์ ์ญ์์ผ๋ก ์ ์ฉํ ๊น?
๋ฏธ๋ค์จ์ด๋ ์์ชฝ๋ถํฐ ๋ฐ๊นฅ์ชฝ์ผ๋ก ๊ฐ์ธ๋ ๊ตฌ์กฐ์ผ. ์คํ ์์๋ ๋ฐ๊นฅ์ชฝ๋ถํฐ ์์ชฝ์ผ๋ก ์งํ๋๊ธฐ ๋๋ฌธ์, ์ฐ๋ฆฌ๊ฐ ์ํ๋ ์์๋๋ก ์คํ๋๋ ค๋ฉด ์ญ์์ผ๋ก ๊ฐ์ธ์ผ ํด!
๋ฐฉ๋ฒ 3: ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ฉ
์ค๋ฌด์์๋ ๊ฒ์ฆ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๋ ์ข์ ์ ํ์ด์ผ. ๋ํ์ ์ธ ๊ฒ Alice๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ง:
import "github.com/justinas/alice"
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/data", DataHandler)
// Alice๋ก ์ฒด์ธ ๊ตฌ์ฑ
chain := alice.New(
LoggingMiddleware,
AuthMiddleware,
CORSMiddleware,
).Then(mux)
http.ListenAndServe(":8080", chain)
}
Alice๋ ๊ฐ๋ ์ฑ๋ ์ข๊ณ , ๋ฏธ๋ค์จ์ด ์ฌ์ฌ์ฉ๋ ์ฝ๊ฒ ๋ง๋ค์ด์ค. ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ฌ๋ฌ API ์๋ํฌ์ธํธ์ ๊ณตํต ๋ฏธ๋ค์จ์ด๋ฅผ ์ ์ฉํ ๋ ํนํ ์ ์ฉํด! ๐ฏ
๐ ์ค์ ๋ฏธ๋ค์จ์ด ์์ ๋ค
์ด๋ก ์ ์ถฉ๋ถํ ๋ดค์ผ๋, ์ค์ ๋ก ์์ฃผ ์ฌ์ฉํ๋ ๋ฏธ๋ค์จ์ด๋ค์ ๋ง๋ค์ด๋ณด์!
์ธ์ฆ ๋ฏธ๋ค์จ์ด (JWT ํ ํฐ ๊ฒ์ฆ)
API ์๋ฒ์์ ๊ฐ์ฅ ์ค์ํ ๋ฏธ๋ค์จ์ด ์ค ํ๋๊ฐ ๋ฐ๋ก ์ธ์ฆ์ด์ผ. JWT ํ ํฐ์ ๊ฒ์ฆํ๋ ๋ฏธ๋ค์จ์ด๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ:
package main
import (
"context"
"fmt"
"net/http"
"strings"
)
type contextKey string
const UserContextKey contextKey = "user"
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Authorization ํค๋ ๊ฐ์ ธ์ค๊ธฐ
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "์ธ์ฆ ํ ํฐ์ด ํ์ํฉ๋๋ค", http.StatusUnauthorized)
return
}
// "Bearer " ์ ๋์ฌ ์ ๊ฑฐ
token := strings.TrimPrefix(authHeader, "Bearer ")
// ํ ํฐ ๊ฒ์ฆ (์ค์ ๋ก๋ JWT ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ)
user, err := validateToken(token)
if err != nil {
http.Error(w, "์ ํจํ์ง ์์ ํ ํฐ์
๋๋ค", http.StatusUnauthorized)
return
}
// ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ปจํ
์คํธ์ ์ ์ฅ
ctx := context.WithValue(r.Context(), UserContextKey, user)
// ๋ค์ ํธ๋ค๋ฌ์ ์ ๋ฌ
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func validateToken(token string) (string, error) {
// ์ค์ ๋ก๋ JWT ๊ฒ์ฆ ๋ก์ง์ด ๋ค์ด๊ฐ์ผ ํจ
if token == "valid-token-123" {
return "user@example.com", nil
}
return "", fmt.Errorf("invalid token")
}
func ProtectedHandler(w http.ResponseWriter, r *http.Request) {
// ์ปจํ
์คํธ์์ ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
user := r.Context().Value(UserContextKey).(string)
fmt.Fprintf(w, "์๋
ํ์ธ์, %s๋!", user)
}
๐ก Context ํ์ฉํ๊ธฐ
Go์ context.Context๋ ์์ฒญ ๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ ํ์ค ๋ฐฉ๋ฒ์ด์ผ. ๋ฏธ๋ค์จ์ด์์ ์ถ์ถํ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ํธ๋ค๋ฌ์ ์ ๋ฌํ ๋ ์์ฃผ ์ ์ฉํด!
CORS ๋ฏธ๋ค์จ์ด
ํ๋ก ํธ์๋์ ๋ฐฑ์๋๊ฐ ๋ค๋ฅธ ๋๋ฉ์ธ์ ์์ ๋ ํ์์ ์ธ CORS ์ฒ๋ฆฌ ๋ฏธ๋ค์จ์ด์ผ:
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// CORS ํค๋ ์ค์
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
// Preflight ์์ฒญ ์ฒ๋ฆฌ
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
Rate Limiting ๋ฏธ๋ค์จ์ด
API ๋จ์ฉ์ ๋ฐฉ์งํ๊ธฐ ์ํ ์์ฒญ ์ ํ ๋ฏธ๋ค์จ์ด๋ ๋ง๋ค์ด๋ณด์:
import (
"net/http"
"sync"
"time"
)
type RateLimiter struct {
requests map[string][]time.Time
mu sync.Mutex
limit int
window time.Duration
}
func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
return &RateLimiter{
requests: make(map[string][]time.Time),
limit: limit,
window: window,
}
}
func (rl *RateLimiter) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr
rl.mu.Lock()
defer rl.mu.Unlock()
now := time.Now()
// ์ด์ ์์ฒญ ๊ธฐ๋ก ๊ฐ์ ธ์ค๊ธฐ
requests := rl.requests[ip]
// ์๊ฐ ์๋์ฐ ๋ฐ์ ์์ฒญ ์ ๊ฑฐ
var validRequests []time.Time
for _, reqTime := range requests {
if now.Sub(reqTime) < rl.window {
validRequests = append(validRequests, reqTime)
}
}
// ์ ํ ํ์ธ
if len(validRequests) >= rl.limit {
http.Error(w, "์์ฒญ ์ ํ ์ด๊ณผ", http.StatusTooManyRequests)
return
}
// ํ์ฌ ์์ฒญ ์ถ๊ฐ
validRequests = append(validRequests, now)
rl.requests[ip] = validRequests
next.ServeHTTP(w, r)
})
}
// ์ฌ์ฉ ์์
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/data", DataHandler)
// 1๋ถ์ 10๊ฐ ์์ฒญ์ผ๋ก ์ ํ
limiter := NewRateLimiter(10, time.Minute)
http.ListenAndServe(":8080", limiter.Middleware(mux))
}
โ ๏ธ ์ฃผ์์ฌํญ
์ Rate Limiter๋ ๊ต์ก์ฉ ์์ ์ผ. ์ค์ ํ๋ก๋์ ์์๋ Redis ๊ฐ์ ์ธ๋ถ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ๊ฑฐ๋, golang.org/x/time/rate ํจํค์ง๋ฅผ ํ์ฉํ๋ ๊ฒ ์ข์!
์๋ฌ ๋ณต๊ตฌ ๋ฏธ๋ค์จ์ด
ํจ๋์ด ๋ฐ์ํด๋ ์๋ฒ๊ฐ ์ฃฝ์ง ์๋๋ก ๋ณดํธํ๋ ๋ฏธ๋ค์จ์ด:
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("ํจ๋ ๋ฐ์: %v", err)
// ์คํ ํธ๋ ์ด์ค ์ถ๋ ฅ
debug.PrintStack()
// 500 ์๋ฌ ์๋ต
http.Error(w, "์๋ฒ ๋ด๋ถ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค",
http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
์ด ๋ฏธ๋ค์จ์ด๋ ํญ์ ์ฒด์ธ์ ๊ฐ์ฅ ๋ฐ๊นฅ์ชฝ์ ๋ฐฐ์นํด์ผ ํด. ๊ทธ๋์ผ ๋ค๋ฅธ ๋ฏธ๋ค์จ์ด๋ ํธ๋ค๋ฌ์์ ๋ฐ์ํ ํจ๋์ ๋ชจ๋ ์ก์ ์ ์๊ฑฐ๋ !
๐จ ๋ฏธ๋ค์จ์ด ์ค๊ณ ํจํด๊ณผ ๋ฒ ์คํธ ํ๋ํฐ์ค
๋ฏธ๋ค์จ์ด๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฉํ๋ ค๋ฉด ๋ช ๊ฐ์ง ์ค๊ณ ์์น์ ์์๋๋ฉด ์ข์!
โ ์ข์ ๋ฏธ๋ค์จ์ด ์ค๊ณ
๋จ์ผ ์ฑ ์: ํ๋์ ๋ฏธ๋ค์จ์ด๋ ํ๋์ ๊ธฐ๋ฅ๋ง
์์ ๋ ๋ฆฝ์ : ๊ฐ๋ฅํ ํ ๋ค๋ฅธ ๋ฏธ๋ค์จ์ด์ ์์กดํ์ง ์๊ธฐ
์ฌ์ฌ์ฉ ๊ฐ๋ฅ: ์ฌ๋ฌ ํ๋ก์ ํธ์์ ์ฌ์ฉํ ์ ์๊ฒ
์ค์ ๊ฐ๋ฅ: ์ต์ ์ผ๋ก ๋์ ์ปค์คํฐ๋ง์ด์ง
โ ํผํด์ผ ํ ํจํด
๋๋ฌด ๋ง์ ์ฑ ์: ํ๋์ ๋ฏธ๋ค์จ์ด๊ฐ ์ฌ๋ฌ ์ผ์ ์ฒ๋ฆฌ
๊ฐํ ๊ฒฐํฉ: ํน์ ๋ฏธ๋ค์จ์ด์ ์์กด
์ ์ญ ์ํ ๋ณ๊ฒฝ: ์์ธก ๋ถ๊ฐ๋ฅํ ๋ถ์์ฉ
๊ณผ๋ํ ์ฒ๋ฆฌ: ์ฑ๋ฅ์ ์ํฅ์ ์ฃผ๋ ๋ฌด๊ฑฐ์ด ์์
์ค์ ๊ฐ๋ฅํ ๋ฏธ๋ค์จ์ด ๋ง๋ค๊ธฐ
๋ฏธ๋ค์จ์ด๋ฅผ ๋ ์ ์ฐํ๊ฒ ๋ง๋ค๋ ค๋ฉด ์ค์ ์ต์ ์ ๋ฐ์ ์ ์๊ฒ ํด์ผ ํด:
type LoggingConfig struct {
IncludeHeaders bool
IncludeBody bool
MaxBodySize int
}
func LoggingMiddlewareWithConfig(config LoggingConfig) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("์์ฒญ: %s %s", r.Method, r.URL.Path)
// ์ค์ ์ ๋ฐ๋ผ ํค๋ ๋ก๊น
if config.IncludeHeaders {
for key, values := range r.Header {
log.Printf(" ํค๋ %s: %v", key, values)
}
}
// ์ค์ ์ ๋ฐ๋ผ ๋ฐ๋ ๋ก๊น
if config.IncludeBody && r.ContentLength > 0 {
// ๋ฐ๋ ์ฝ๊ธฐ ๋ก์ง...
}
next.ServeHTTP(w, r)
log.Printf("์๋ฃ: %v", time.Since(start))
})
}
}
// ์ฌ์ฉ ์์
func main() {
config := LoggingConfig{
IncludeHeaders: true,
IncludeBody: false,
MaxBodySize: 1024,
}
mux := http.NewServeMux()
mux.HandleFunc("/api/data", DataHandler)
handler := LoggingMiddlewareWithConfig(config)(mux)
http.ListenAndServe(":8080", handler)
}
๋ฏธ๋ค์จ์ด ์คํ ์์ ๊ฐ์ด๋
๋ฏธ๋ค์จ์ด์ ์์๋ ์ ๋ง ์ค์ํด! ์ผ๋ฐ์ ์ผ๋ก ์ถ์ฒํ๋ ์์๋ ์ด๋:
1๏ธโฃ Recovery (ํจ๋ ๋ณต๊ตฌ) - ๊ฐ์ฅ ๋จผ์ , ๋ชจ๋ ์๋ฌ๋ฅผ ์ก๊ธฐ ์ํด
2๏ธโฃ Logging (๋ก๊น ) - ๋ชจ๋ ์์ฒญ์ ๊ธฐ๋ก
3๏ธโฃ CORS - ํฌ๋ก์ค ์ค๋ฆฌ์ง ์ฒ๋ฆฌ
4๏ธโฃ Rate Limiting - ์์ฒญ ์ ํ
5๏ธโฃ Authentication (์ธ์ฆ) - ์ฌ์ฉ์ ์ ์ ํ์ธ
6๏ธโฃ Authorization (๊ถํ) - ์ ๊ทผ ๊ถํ ์ฒดํฌ
7๏ธโฃ Request Validation - ์์ฒญ ๋ฐ์ดํฐ ๊ฒ์ฆ
8๏ธโฃ Business Logic Handler - ์ค์ ๋น์ฆ๋์ค ๋ก์ง
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/users", UsersHandler)
// ๊ถ์ฅ ์์๋๋ก ์ฒด์ธ ๊ตฌ์ฑ
chain := Chain(
RecoveryMiddleware, // 1. ํจ๋ ๋ณต๊ตฌ
LoggingMiddleware, // 2. ๋ก๊น
CORSMiddleware, // 3. CORS
RateLimitMiddleware, // 4. Rate Limiting
AuthMiddleware, // 5. ์ธ์ฆ
AuthorizationMiddleware, // 6. ๊ถํ
)
http.ListenAndServe(":8080", chain(mux))
}
๐ ๊ณ ๊ธ ํจํด: ์กฐ๊ฑด๋ถ ๋ฏธ๋ค์จ์ด์ ๋ผ์ฐํธ๋ณ ์ ์ฉ
๋ชจ๋ ๋ผ์ฐํธ์ ๊ฐ์ ๋ฏธ๋ค์จ์ด๋ฅผ ์ ์ฉํ ํ์๋ ์์ด. ํน์ ์กฐ๊ฑด์ด๋ ๋ผ์ฐํธ์๋ง ๋ฏธ๋ค์จ์ด๋ฅผ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์!
์กฐ๊ฑด๋ถ ๋ฏธ๋ค์จ์ด
ํน์ ์กฐ๊ฑด์์๋ง ๋ฏธ๋ค์จ์ด๋ฅผ ์คํํ๊ณ ์ถ์ ๋๊ฐ ์์ด. ์๋ฅผ ๋ค์ด, ํน์ ๊ฒฝ๋ก์์๋ง ์ธ์ฆ์ ์ฒดํฌํ๋ค๊ฑฐ๋:
func ConditionalMiddleware(condition func(*http.Request) bool,
middleware func(http.Handler) http.Handler) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if condition(r) {
// ์กฐ๊ฑด์ด ์ฐธ์ด๋ฉด ๋ฏธ๋ค์จ์ด ์ ์ฉ
middleware(next).ServeHTTP(w, r)
} else {
// ์กฐ๊ฑด์ด ๊ฑฐ์ง์ด๋ฉด ๋ฐ๋ก ๋ค์ ํธ๋ค๋ฌ๋ก
next.ServeHTTP(w, r)
}
})
}
}
// ์ฌ์ฉ ์์
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/public", PublicHandler)
mux.HandleFunc("/api/private", PrivateHandler)
// /api/๋ก ์์ํ๋ ๊ฒฝ๋ก์๋ง ์ธ์ฆ ์ ์ฉ
authCondition := func(r *http.Request) bool {
return strings.HasPrefix(r.URL.Path, "/api/")
}
handler := ConditionalMiddleware(authCondition, AuthMiddleware)(mux)
http.ListenAndServe(":8080", handler)
}
๋ผ์ฐํธ ๊ทธ๋ฃน๋ณ ๋ฏธ๋ค์จ์ด
๋ผ์ฐํธ๋ฅผ ๊ทธ๋ฃนํํด์ ๊ฐ ๊ทธ๋ฃน์ ๋ค๋ฅธ ๋ฏธ๋ค์จ์ด๋ฅผ ์ ์ฉํ ์๋ ์์ด:
type Router struct {
mux *http.ServeMux
middlewares []Middleware
}
func NewRouter() *Router {
return &Router{
mux: http.NewServeMux(),
middlewares: []Middleware{},
}
}
func (rt *Router) Use(middleware Middleware) {
rt.middlewares = append(rt.middlewares, middleware)
}
func (rt *Router) Handle(pattern string, handler http.HandlerFunc) {
// ํ์ฌ ๋ผ์ฐํฐ์ ๋ฏธ๋ค์จ์ด ์ฒด์ธ ์ ์ฉ
wrapped := handler
for i := len(rt.middlewares) - 1; i >= 0; i-- {
wrapped = rt.middlewares[i](wrapped).(http.HandlerFunc)
}
rt.mux.HandleFunc(pattern, wrapped)
}
func (rt *Router) Group(prefix string) *Router {
// ์๋ก์ด ์๋ธ ๋ผ์ฐํฐ ์์ฑ (๋ถ๋ชจ์ ๋ฏธ๋ค์จ์ด ์์)
subRouter := &Router{
mux: rt.mux,
middlewares: append([]Middleware{}, rt.middlewares...),
}
return subRouter
}
// ์ฌ์ฉ ์์
func main() {
router := NewRouter()
// ์ ์ญ ๋ฏธ๋ค์จ์ด
router.Use(LoggingMiddleware)
router.Use(RecoveryMiddleware)
// ๊ณต๊ฐ ๋ผ์ฐํธ
router.Handle("/", HomeHandler)
router.Handle("/about", AboutHandler)
// API ๊ทธ๋ฃน (์ธ์ฆ ํ์)
apiRouter := router.Group("/api")
apiRouter.Use(AuthMiddleware)
apiRouter.Use(RateLimitMiddleware)
apiRouter.Handle("/api/users", UsersHandler)
apiRouter.Handle("/api/posts", PostsHandler)
// ๊ด๋ฆฌ์ ๊ทธ๋ฃน (์ธ์ฆ + ๊ถํ ํ์)
adminRouter := apiRouter.Group("/api/admin")
adminRouter.Use(AdminAuthorizationMiddleware)
adminRouter.Handle("/api/admin/settings", SettingsHandler)
http.ListenAndServe(":8080", router.mux)
}
๐ฏ ์ค์ ํ!
์ค์ ํ๋ก์ ํธ์์๋ gorilla/mux๋ chi ๊ฐ์ ๋ผ์ฐํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ฐ ๊ธฐ๋ฅ๋ค์ด ์ด๋ฏธ ๊ตฌํ๋์ด ์์ด์ ํจ์ฌ ํธํด! ํ์ง๋ง ์๋ฆฌ๋ฅผ ์ดํดํ๋ ๊ฑด ์ ๋ง ์ค์ํ์ง. ๐
๐ ๋ฏธ๋ค์จ์ด ์ฑ๋ฅ ์ต์ ํ
๋ฏธ๋ค์จ์ด๋ ๋ชจ๋ ์์ฒญ๋ง๋ค ์คํ๋๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ์ด ์ ๋ง ์ค์ํด. ์ต์ ํ ๋ฐฉ๋ฒ์ ์์๋ณด์!
1. ๋ถํ์ํ ์์ ์ต์ํ
โ ๋นํจ์จ์ ์ธ ์ฝ๋:
func BadLoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ๋งค ์์ฒญ๋ง๋ค ํ์ผ ์ด๊ธฐ/๋ซ๊ธฐ (๋นํจ์จ์ !)
file, _ := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE, 0644)
defer file.Close()
logger := log.New(file, "", log.LstdFlags)
logger.Printf("%s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
โ ํจ์จ์ ์ธ ์ฝ๋:
func GoodLoggingMiddleware(logger *log.Logger) func(http.Handler) http.Handler {
// ๋ก๊ฑฐ๋ฅผ ํ ๋ฒ๋ง ์์ฑ
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Printf("%s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
}
func main() {
// ์ ํ๋ฆฌ์ผ์ด์
์์ ์ ํ ๋ฒ๋ง ํ์ผ ์ด๊ธฐ
file, _ := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE, 0644)
defer file.Close()
logger := log.New(file, "", log.LstdFlags)
mux := http.NewServeMux()
handler := GoodLoggingMiddleware(logger)(mux)
http.ListenAndServe(":8080", handler)
}
2. ์๋ต ๋ํผ๋ก ํจ์จ์ ์ธ ๋ก๊น
์๋ต ์ํ ์ฝ๋๋ ํฌ๊ธฐ๋ฅผ ๋ก๊น ํ๋ ค๋ฉด ResponseWriter๋ฅผ ๋ํํด์ผ ํด:
type ResponseWriterWrapper struct {
http.ResponseWriter
statusCode int
bytesWritten int
}
func NewResponseWriterWrapper(w http.ResponseWriter) *ResponseWriterWrapper {
return &ResponseWriterWrapper{
ResponseWriter: w,
statusCode: http.StatusOK, // ๊ธฐ๋ณธ๊ฐ
}
}
func (rw *ResponseWriterWrapper) WriteHeader(statusCode int) {
rw.statusCode = statusCode
rw.ResponseWriter.WriteHeader(statusCode)
}
func (rw *ResponseWriterWrapper) Write(b []byte) (int, error) {
n, err := rw.ResponseWriter.Write(b)
rw.bytesWritten += n
return n, err
}
func DetailedLoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// ResponseWriter ๋ํ
wrapped := NewResponseWriterWrapper(w)
// ๋ค์ ํธ๋ค๋ฌ ์คํ
next.ServeHTTP(wrapped, r)
// ์์ธ ๋ก๊น
duration := time.Since(start)
log.Printf("%s %s - Status: %d, Size: %d bytes, Duration: %v",
r.Method, r.URL.Path,
wrapped.statusCode,
wrapped.bytesWritten,
duration)
})
}
3. ์ปจํ ์คํธ ํ์ฉํ ๋ฐ์ดํฐ ์ ๋ฌ
๋ฏธ๋ค์จ์ด ๊ฐ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ์ ๋ฌํ๋ ค๋ฉด Context๋ฅผ ํ์ฉํด:
type RequestIDKey struct{}
func RequestIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ๊ณ ์ ์์ฒญ ID ์์ฑ
requestID := generateRequestID()
// ์ปจํ
์คํธ์ ์ ์ฅ
ctx := context.WithValue(r.Context(), RequestIDKey{}, requestID)
// ์๋ต ํค๋์๋ ์ถ๊ฐ
w.Header().Set("X-Request-ID", requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ์ปจํ
์คํธ์์ ์์ฒญ ID ๊ฐ์ ธ์ค๊ธฐ
requestID := r.Context().Value(RequestIDKey{}).(string)
log.Printf("[%s] %s %s", requestID, r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
func generateRequestID() string {
return fmt.Sprintf("%d", time.Now().UnixNano())
}
๐ก ์ฑ๋ฅ ์ธก์ ํ๊ธฐ
๋ฏธ๋ค์จ์ด์ ์ฑ๋ฅ ์ํฅ์ ์ธก์ ํ๋ ค๋ฉด ๋ฒค์น๋งํฌ๋ฅผ ์์ฑํด๋ด. Go์ testing ํจํค์ง๋ก ์ฝ๊ฒ ํ ์ ์์ด!
func BenchmarkMiddleware(b *testing.B) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
middleware := LoggingMiddleware(handler)
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
b.ResetTimer()
for i := 0; i < b.N; i++ {
middleware.ServeHTTP(w, req)
}
}
๐ง ์ค์ ํ๋ก์ ํธ ๊ตฌ์กฐ
์ค์ ํ๋ก์ ํธ์์ ๋ฏธ๋ค์จ์ด๋ฅผ ์ด๋ป๊ฒ ๊ตฌ์กฐํํ๋ฉด ์ข์๊น? ๊น๋ํ ํ๋ก์ ํธ ๊ตฌ์กฐ๋ฅผ ์ ์ํด๋ณผ๊ฒ!
myproject/
โโโ main.go
โโโ middleware/
โ โโโ auth.go # ์ธ์ฆ ๋ฏธ๋ค์จ์ด
โ โโโ logging.go # ๋ก๊น
๋ฏธ๋ค์จ์ด
โ โโโ cors.go # CORS ๋ฏธ๋ค์จ์ด
โ โโโ recovery.go # ํจ๋ ๋ณต๊ตฌ ๋ฏธ๋ค์จ์ด
โ โโโ ratelimit.go # Rate Limiting ๋ฏธ๋ค์จ์ด
โ โโโ chain.go # ์ฒด์ธ ํฌํผ ํจ์
โโโ handlers/
โ โโโ users.go # ์ฌ์ฉ์ ํธ๋ค๋ฌ
โ โโโ posts.go # ๊ฒ์๊ธ ํธ๋ค๋ฌ
โ โโโ admin.go # ๊ด๋ฆฌ์ ํธ๋ค๋ฌ
โโโ config/
โ โโโ config.go # ์ค์ ๊ด๋ฆฌ
โโโ utils/
โโโ context.go # ์ปจํ
์คํธ ํฌํผ
middleware/chain.go
package middleware
import "net/http"
type Middleware func(http.Handler) http.Handler
func Chain(middlewares ...Middleware) Middleware {
return func(final http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
final = middlewares[i](final)
}
return final
}
}
// ๊ณตํต ๋ฏธ๋ค์จ์ด ์ฒด์ธ
func CommonChain() Middleware {
return Chain(
Recovery,
Logging,
CORS,
)
}
// API ๋ฏธ๋ค์จ์ด ์ฒด์ธ
func APIChain() Middleware {
return Chain(
CommonChain(),
RateLimit(100, time.Minute),
Auth,
)
}
// ๊ด๋ฆฌ์ ๋ฏธ๋ค์จ์ด ์ฒด์ธ
func AdminChain() Middleware {
return Chain(
APIChain(),
AdminAuthorization,
)
}
main.go
package main
import (
"log"
"net/http"
"myproject/handlers"
"myproject/middleware"
)
func main() {
mux := http.NewServeMux()
// ๊ณต๊ฐ ๋ผ์ฐํธ
mux.HandleFunc("/", handlers.Home)
mux.HandleFunc("/about", handlers.About)
// API ๋ผ์ฐํธ (์ธ์ฆ ํ์)
apiMux := http.NewServeMux()
apiMux.HandleFunc("/api/users", handlers.Users)
apiMux.HandleFunc("/api/posts", handlers.Posts)
mux.Handle("/api/", middleware.APIChain()(apiMux))
// ๊ด๋ฆฌ์ ๋ผ์ฐํธ (์ธ์ฆ + ๊ถํ ํ์)
adminMux := http.NewServeMux()
adminMux.HandleFunc("/api/admin/settings", handlers.AdminSettings)
mux.Handle("/api/admin/", middleware.AdminChain()(adminMux))
// ๊ณตํต ๋ฏธ๋ค์จ์ด ์ ์ฉ
handler := middleware.CommonChain()(mux)
log.Println("์๋ฒ ์์: http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}
์ด๋ฐ ๊ตฌ์กฐ๋ก ๋ง๋ค๋ฉด ์ฝ๋๊ฐ ์ ๋ง ๊น๋ํด์ง๊ณ ์ ์ง๋ณด์ํ๊ธฐ๋ ์ฌ์์ ธ! ๐
๐ ์ธ๊ธฐ ์๋ Go ์น ํ๋ ์์ํฌ์ ๋ฏธ๋ค์จ์ด
ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง์ผ๋ก๋ ์ถฉ๋ถํ์ง๋ง, ์ค๋ฌด์์๋ ๊ฒ์ฆ๋ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์. ๋ํ์ ์ธ ํ๋ ์์ํฌ๋ค์ ๋ฏธ๋ค์จ์ด ์ฌ์ฉ๋ฒ์ ๊ฐ๋จํ ์ดํด๋ณด์!
Gin ํ๋ ์์ํฌ
Gin์ Go์์ ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ์น ํ๋ ์์ํฌ ์ค ํ๋์ผ. ๋ฏธ๋ค์จ์ด ์ฌ์ฉ์ด ์ ๋ง ๊ฐ๋จํด:
package main
import (
"github.com/gin-gonic/gin"
"log"
"time"
)
// ์ปค์คํ
๋ฏธ๋ค์จ์ด
func CustomLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
// ์์ฒญ ์ฒ๋ฆฌ ์
log.Printf("์์ฒญ ์์: %s %s", c.Request.Method, c.Request.URL.Path)
// ๋ค์ ํธ๋ค๋ฌ ์คํ
c.Next()
// ์์ฒญ ์ฒ๋ฆฌ ํ
duration := time.Since(start)
log.Printf("์์ฒญ ์๋ฃ: %d (%v)", c.Writer.Status(), duration)
}
}
func main() {
r := gin.Default() // ๊ธฐ๋ณธ ๋ฏธ๋ค์จ์ด ํฌํจ (Logger, Recovery)
// ์ ์ญ ๋ฏธ๋ค์จ์ด
r.Use(CustomLogger())
// ๋ผ์ฐํธ๋ณ ๋ฏธ๋ค์จ์ด
r.GET("/public", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "๊ณต๊ฐ ํ์ด์ง"})
})
// ๊ทธ๋ฃน๋ณ ๋ฏธ๋ค์จ์ด
authorized := r.Group("/api")
authorized.Use(AuthMiddleware())
{
authorized.GET("/users", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "์ฌ์ฉ์ ๋ชฉ๋ก"})
})
}
r.Run(":8080")
}
Echo ํ๋ ์์ํฌ
Echo๋ ๋ฏธ๋ค์จ์ด ์์คํ ์ด ๊ฐ๋ ฅํด:
package main
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// ๊ธฐ๋ณธ ๋ฏธ๋ค์จ์ด
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// CORS ๋ฏธ๋ค์จ์ด
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{echo.GET, echo.POST, echo.PUT, echo.DELETE},
}))
// Rate Limiting
e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)))
// ๋ผ์ฐํธ
e.GET("/", func(c echo.Context) error {
return c.String(200, "Hello, World!")
})
// ๊ทธ๋ฃน๋ณ ๋ฏธ๋ค์จ์ด
api := e.Group("/api")
api.Use(middleware.JWT([]byte("secret")))
api.GET("/users", func(c echo.Context) error {
return c.JSON(200, map[string]string{"message": "์ฌ์ฉ์ ๋ชฉ๋ก"})
})
e.Start(":8080")
}
Chi ๋ผ์ฐํฐ
Chi๋ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํธํ๋๋ฉด์๋ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ์ ๊ณตํด:
package main
import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"net/http"
)
func main() {
r := chi.NewRouter()
// ๊ธฐ๋ณธ ๋ฏธ๋ค์จ์ด
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
// Timeout ๋ฏธ๋ค์จ์ด
r.Use(middleware.Timeout(60 * time.Second))
// ๊ณต๊ฐ ๋ผ์ฐํธ
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ํํ์ด์ง"))
})
// API ๊ทธ๋ฃน
r.Route("/api", func(r chi.Router) {
r.Use(AuthMiddleware) // ์ด ๊ทธ๋ฃน์๋ง ์ ์ฉ
r.Get("/users", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("์ฌ์ฉ์ ๋ชฉ๋ก"))
})
// ์ค์ฒฉ ๊ทธ๋ฃน
r.Route("/admin", func(r chi.Router) {
r.Use(AdminMiddleware) // ์ถ๊ฐ ๋ฏธ๋ค์จ์ด
r.Get("/settings", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("๊ด๋ฆฌ์ ์ค์ "))
})
})
})
http.ListenAndServe(":8080", r)
}
๐ฏ ํ๋ ์์ํฌ ์ ํ ๊ฐ์ด๋
ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ: ๊ฐ๋จํ ํ๋ก์ ํธ, ์ต๋ํ์ ์ ์ด๊ฐ ํ์ํ ๋
Gin: ๋น ๋ฅธ ๊ฐ๋ฐ, ํ๋ถํ ๊ธฐ๋ฅ, ํฐ ์ปค๋ฎค๋ํฐ
Echo: ์ฑ๋ฅ ์ค์, ๋ฏธ๋ค์จ์ด ์ํ๊ณ๊ฐ ์ข์
Chi: ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํธํ, ๋ฏธ๋ค์จ์ด ์กฐํฉ์ด ์ ์ฐํจ
๐ ๋๋ฒ๊น ๊ณผ ํ ์คํ
๋ฏธ๋ค์จ์ด๋ฅผ ์ ๋๋ก ํ ์คํธํ๋ ๊ฒ๋ ์ค์ํด! ์ด๋ป๊ฒ ํ ์คํธํ๋์ง ์์๋ณด์.
๋ฏธ๋ค์จ์ด ๋จ์ ํ ์คํธ
package middleware
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestLoggingMiddleware(t *testing.T) {
// ํ
์คํธ์ฉ ํธ๋ค๋ฌ
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
// ๋ฏธ๋ค์จ์ด ์ ์ฉ
middleware := LoggingMiddleware(handler)
// ํ
์คํธ ์์ฒญ ์์ฑ
req := httptest.NewRequest("GET", "/test", nil)
rec := httptest.NewRecorder()
// ์คํ
middleware.ServeHTTP(rec, req)
// ๊ฒ์ฆ
if rec.Code != http.StatusOK {
t.Errorf("์์ ์ํ ์ฝ๋: %d, ์ค์ : %d", http.StatusOK, rec.Code)
}
if rec.Body.String() != "OK" {
t.Errorf("์์ ์๋ต: OK, ์ค์ : %s", rec.Body.String())
}
}
func TestAuthMiddleware(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("์ธ์ฆ ์ฑ๊ณต"))
})
middleware := AuthMiddleware(handler)
tests := []struct {
name string
authHeader string
expectedStatus int
expectedBody string
}{
{
name: "์ ํจํ ํ ํฐ",
authHeader: "Bearer valid-token-123",
expectedStatus: http.StatusOK,
expectedBody: "์ธ์ฆ ์ฑ๊ณต",
},
{
name: "ํ ํฐ ์์",
authHeader: "",
expectedStatus: http.StatusUnauthorized,
expectedBody: "์ธ์ฆ ํ ํฐ์ด ํ์ํฉ๋๋ค\n",
},
{
name: "์ ํจํ์ง ์์ ํ ํฐ",
authHeader: "Bearer invalid-token",
expectedStatus: http.StatusUnauthorized,
expectedBody: "์ ํจํ์ง ์์ ํ ํฐ์
๋๋ค\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "/test", nil)
if tt.authHeader != "" {
req.Header.Set("Authorization", tt.authHeader)
}
rec := httptest.NewRecorder()
middleware.ServeHTTP(rec, req)
if rec.Code != tt.expectedStatus {
t.Errorf("์ํ ์ฝ๋: ์์ %d, ์ค์ %d",
tt.expectedStatus, rec.Code)
}
if rec.Body.String() != tt.expectedBody {
t.Errorf("์๋ต ๋ณธ๋ฌธ: ์์ %s, ์ค์ %s",
tt.expectedBody, rec.Body.String())
}
})
}
}
ํตํฉ ํ ์คํธ
์ฌ๋ฌ ๋ฏธ๋ค์จ์ด๊ฐ ํจ๊ป ๋์ํ๋์ง ํ ์คํธํ๋ ๊ฒ๋ ์ค์ํด:
func TestMiddlewareChain(t *testing.T) {
// ์ต์ข
ํธ๋ค๋ฌ
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ์ปจํ
์คํธ์์ ์ฌ์ฉ์ ์ ๋ณด ํ์ธ
user := r.Context().Value(UserContextKey)
if user == nil {
t.Error("์ฌ์ฉ์ ์ ๋ณด๊ฐ ์ปจํ
์คํธ์ ์์")
}
w.Write([]byte("์ฑ๊ณต"))
})
// ๋ฏธ๋ค์จ์ด ์ฒด์ธ ๊ตฌ์ฑ
chain := Chain(
LoggingMiddleware,
AuthMiddleware,
)(handler)
// ํ
์คํธ ์์ฒญ
req := httptest.NewRequest("GET", "/test", nil)
req.Header.Set("Authorization", "Bearer valid-token-123")
rec := httptest.NewRecorder()
chain.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("์์ ์ํ ์ฝ๋: %d, ์ค์ : %d", http.StatusOK, rec.Code)
}
}
๐ก ํ ์คํธ ํ
โข httptest ํจํค์ง๋ฅผ ์ ๊ทน ํ์ฉํด
โข ๊ฐ ๋ฏธ๋ค์จ์ด๋ ๋
๋ฆฝ์ ์ผ๋ก ํ
์คํธ ๊ฐ๋ฅํด์ผ ํด
โข ์๋ฌ ์ผ์ด์ค๋ ๊ผญ ํ
์คํธํด
โข ํตํฉ ํ
์คํธ๋ก ์ ์ฒด ํ์ดํ๋ผ์ธ์ ๊ฒ์ฆํด
๐ ์ค์ ์์ : ์์ ํ API ์๋ฒ
์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ด์ฉ์ ๋ชจ๋ ํ์ฉํด์ ์ค์ ์์ ์ฌ์ฉํ ์ ์๋ ์์ ํ API ์๋ฒ๋ฅผ ๋ง๋ค์ด๋ณด์! ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์ ๋ฐฑ์๋๋ฅผ ๋ง๋ ๋ค๊ณ ์๊ฐํด๋ด. ๐
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"time"
)
// ============ ์ปจํ
์คํธ ํค ============
type contextKey string
const (
UserContextKey contextKey = "user"
RequestIDContextKey contextKey = "requestID"
)
// ============ ๋ฏธ๋ค์จ์ด ============
// ์์ฒญ ID ์์ฑ ๋ฏธ๋ค์จ์ด
func RequestIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := fmt.Sprintf("%d", time.Now().UnixNano())
ctx := context.WithValue(r.Context(), RequestIDContextKey, requestID)
w.Header().Set("X-Request-ID", requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// ๋ก๊น
๋ฏธ๋ค์จ์ด
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
requestID := r.Context().Value(RequestIDContextKey).(string)
wrapped := &responseWriter{ResponseWriter: w, statusCode: 200}
log.Printf("[%s] ์์ฒญ: %s %s", requestID, r.Method, r.URL.Path)
next.ServeHTTP(wrapped, r)
duration := time.Since(start)
log.Printf("[%s] ์๋ต: %d (%v)", requestID, wrapped.statusCode, duration)
})
}
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
// ํจ๋ ๋ณต๊ตฌ ๋ฏธ๋ค์จ์ด
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
requestID := r.Context().Value(RequestIDContextKey).(string)
log.Printf("[%s] ํจ๋ ๋ฐ์: %v", requestID, err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{
"error": "์๋ฒ ๋ด๋ถ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค",
"requestId": requestID,
})
}
}()
next.ServeHTTP(w, r)
})
}
// CORS ๋ฏธ๋ค์จ์ด
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
// ์ธ์ฆ ๋ฏธ๋ค์จ์ด
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
respondError(w, "์ธ์ฆ์ด ํ์ํฉ๋๋ค", http.StatusUnauthorized)
return
}
token := strings.TrimPrefix(authHeader, "Bearer ")
// ์ค์ ๋ก๋ JWT ๊ฒ์ฆ
if token != "valid-token-123" {
respondError(w, "์ ํจํ์ง ์์ ํ ํฐ์
๋๋ค", http.StatusUnauthorized)
return
}
// ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ปจํ
์คํธ์ ์ ์ฅ
ctx := context.WithValue(r.Context(), UserContextKey, "user@example.com")
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// JSON ์๋ต ๋ฏธ๋ค์จ์ด
func JSONMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
next.ServeHTTP(w, r)
})
}
// ============ ํธ๋ค๋ฌ ============
func HomeHandler(w http.ResponseWriter, r *http.Request) {
response := map[string]string{
"message": "์ฌ๋ฅ๋ท API ์๋ฒ์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค!",
"version": "1.0.0",
}
json.NewEncoder(w).Encode(response)
}
func UsersHandler(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value(UserContextKey).(string)
response := map[string]interface{}{
"users": []map[string]string{
{"id": "1", "name": "ํ๊ธธ๋", "email": "hong@example.com"},
{"id": "2", "name": "๊น์ฒ ์", "email": "kim@example.com"},
},
"requestedBy": user,
}
json.NewEncoder(w).Encode(response)
}
func ProfileHandler(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value(UserContextKey).(string)
response := map[string]string{
"email": user,
"name": "ํ๊ธธ๋",
"role": "developer",
}
json.NewEncoder(w).Encode(response)
}
// ============ ํฌํผ ํจ์ ============
func respondError(w http.ResponseWriter, message string, statusCode int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(map[string]string{
"error": message,
})
}
type Middleware func(http.Handler) http.Handler
func Chain(middlewares ...Middleware) Middleware {
return func(final http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
final = middlewares[i](final)
}
return final
}
}
// ============ ๋ฉ์ธ ํจ์ ============
func main() {
mux := http.NewServeMux()
// ๊ณต๊ฐ ๋ผ์ฐํธ
mux.HandleFunc("/", HomeHandler)
// API ๋ผ์ฐํธ (์ธ์ฆ ํ์)
apiMux := http.NewServeMux()
apiMux.HandleFunc("/api/users", UsersHandler)
apiMux.HandleFunc("/api/profile", ProfileHandler)
// API ๋ฏธ๋ค์จ์ด ์ฒด์ธ
apiChain := Chain(
JSONMiddleware,
AuthMiddleware,
)
mux.Handle("/api/", apiChain(apiMux))
// ์ ์ญ ๋ฏธ๋ค์จ์ด ์ฒด์ธ
globalChain := Chain(
RecoveryMiddleware,
RequestIDMiddleware,
LoggingMiddleware,
CORSMiddleware,
)
handler := globalChain(mux)
log.Println("๐ ์๋ฒ ์์: http://localhost:8080")
log.Println("๐ ํ
์คํธ ๋ช
๋ น์ด:")
log.Println(" curl http://localhost:8080/")
log.Println(" curl -H \"Authorization: Bearer valid-token-123\" http://localhost:8080/api/users")
if err := http.ListenAndServe(":8080", handler); err != nil {
log.Fatal(err)
}
}
์ด ์๋ฒ๋ฅผ ์คํํ๊ณ ํ ์คํธํด๋ณด๋ฉด:
๊ณต๊ฐ ์๋ํฌ์ธํธ ํ ์คํธ:
curl http://localhost:8080/
# ์๋ต:
{
"message": "์ฌ๋ฅ๋ท API ์๋ฒ์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค!",
"version": "1.0.0"
}
์ธ์ฆ์ด ํ์ํ ์๋ํฌ์ธํธ ํ ์คํธ:
curl -H "Authorization: Bearer valid-token-123" \
http://localhost:8080/api/users
# ์๋ต:
{
"users": [
{"id": "1", "name": "ํ๊ธธ๋", "email": "hong@example.com"},
{"id": "2", "name": "๊น์ฒ ์", "email": "kim@example.com"}
],
"requestedBy": "user@example.com"
}
์๋ฒ ๋ก๊ทธ๋ฅผ ๋ณด๋ฉด ๊ฐ ๋ฏธ๋ค์จ์ด๊ฐ ์์๋๋ก ์คํ๋๋ ๊ฑธ ํ์ธํ ์ ์์ด:
[1705308045123456789] ์๋ต: 200 (2.5ms)
๐ก ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ฌ๊ธฐ๊น์ง ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด. ๐ Go ์ธ์ด์ ๋ฏธ๋ค์จ์ด์ HTTP ํธ๋ค๋ฌ ์ฒด์ธ์ ๋ํด ์ ๋ง ๋ง์ ๊ฑธ ๋ฐฐ์ ์ง?
๐ฏ ํต์ฌ ์์ฝ
1. ๋ฏธ๋ค์จ์ด์ ๋ณธ์ง
๋ฏธ๋ค์จ์ด๋ HTTP ํธ๋ค๋ฌ๋ฅผ ๊ฐ์ธ์ ์ถ๊ฐ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ํจ์์ผ. ๋ก๊น
, ์ธ์ฆ, ์๋ฌ ์ฒ๋ฆฌ ๊ฐ์ ๊ณตํต ๊ด์ฌ์ฌ๋ฅผ ๋ถ๋ฆฌํ ์ ์์ด.
2. ์ฒด์ด๋ ํจํด
์ฌ๋ฌ ๋ฏธ๋ค์จ์ด๋ฅผ ์ฒด์ธ์ฒ๋ผ ์ฐ๊ฒฐํด์ ๊ฐ๋ ฅํ ์์ฒญ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ์ ๋ง๋ค ์ ์์ด. ์์๊ฐ ์ ๋ง ์ค์ํ๋ค๋ ๊ฑธ ์์ง ๋ง!
3. ์ค์ ํ์ฉ
์ธ์ฆ, CORS, Rate Limiting, ๋ก๊น
๋ฑ ์ค๋ฌด์์ ์์ฃผ ์ฌ์ฉํ๋ ๋ฏธ๋ค์จ์ด๋ค์ ์ง์ ๊ตฌํํด๋ดค์ด. ์ด์ ์ค์ ํ๋ก์ ํธ์ ๋ฐ๋ก ์ ์ฉํ ์ ์์ ๊ฑฐ์ผ.
4. ์ฑ๋ฅ๊ณผ ํ
์คํธ
๋ฏธ๋ค์จ์ด๋ ๋ชจ๋ ์์ฒญ๋ง๋ค ์คํ๋๋๊น ์ฑ๋ฅ ์ต์ ํ๊ฐ ์ค์ํด. ๊ทธ๋ฆฌ๊ณ ๋จ์ ํ
์คํธ์ ํตํฉ ํ
์คํธ๋ก ์์ ์ฑ์ ํ๋ณดํด์ผ ํด.
โจ ๋ค์ ๋จ๊ณ๋ก!
์ด์ ๊ธฐ๋ณธ์ ๋ง์คํฐํ์ผ๋, ๋ค์ ๋จ๊ณ๋ก ๋์๊ฐ๋ณด์:
โข ์ค์ JWT ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ์ธ์ฆ ๊ตฌํ
โข Redis๋ฅผ ํ์ฉํ ๋ถ์ฐ Rate Limiting
โข ๋ฉํธ๋ฆญ ์์ง๊ณผ ๋ชจ๋ํฐ๋ง ๋ฏธ๋ค์จ์ด
โข GraphQL์ด๋ gRPC์ ๋ฏธ๋ค์จ์ด ํตํฉ
โข ๋ง์ดํฌ๋ก์๋น์ค ํ๊ฒฝ์์์ ๋ฏธ๋ค์จ์ด ํจํด
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์ ๊ฐ๋ฐํ ๋๋ ์ด๋ฐ ๋ฏธ๋ค์จ์ด ํจํด์ ์ ํ์ฉํ๋ฉด ์ฝ๋์ ํ์ง๊ณผ ์ ์ง๋ณด์์ฑ์ด ์์ฒญ๋๊ฒ ์ข์์ ธ. ๊ฐ ๊ธฐ๋ฅ์ด ๋ ๋ฆฝ์ ์ด๊ณ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๋๊น ์๋ก์ด ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ธฐ๋ ํจ์ฌ ์ฌ์์ง์ง! ๐
Go ์ธ์ด์ ๋ฏธ๋ค์จ์ด๋ ๋จ์ํ๋ฉด์๋ ๊ฐ๋ ฅํด. ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง์ผ๋ก๋ ์ถฉ๋ถํ ํ๋ก๋์ ๋ ๋ฒจ์ ์๋ฒ๋ฅผ ๋ง๋ค ์ ์์ด. ๋ฌผ๋ก Gin, Echo, Chi ๊ฐ์ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ ํธํ์ง๋ง, ๊ธฐ๋ณธ ์๋ฆฌ๋ฅผ ์ดํดํ๋ ๊ฒ ๊ฐ์ฅ ์ค์ํด!
์ด์ ์ง์ ํ๋ก์ ํธ์ ์ ์ฉํด๋ณด๋ฉด์ ๊ฒฝํ์ ์์๋ด. ์ฒ์์ ์ด๋ ค์ธ ์ ์์ง๋ง, ๊ณ์ ์ฐ์ตํ๋ค ๋ณด๋ฉด ์ด๋์ ๋ฏธ๋ค์จ์ด ๋ง์คํฐ๊ฐ ๋์ด ์์ ๊ฑฐ์ผ! ๐ช
๊ถ๊ธํ ์ ์ด ์๊ฑฐ๋ ๋ ๊น์ด ์๊ณ ์ถ์ ๋ด์ฉ์ด ์๋ค๋ฉด, Go ์ปค๋ฎค๋ํฐ์ ์ง๋ฌธํ๊ฑฐ๋ ๊ณต์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด๋ด. ๊ทธ๋ฆฌ๊ณ ์คํ์์ค ํ๋ก์ ํธ๋ค์ ์ฝ๋๋ฅผ ์ฝ์ด๋ณด๋ ๊ฒ๋ ์ ๋ง ์ข์ ํ์ต ๋ฐฉ๋ฒ์ด์ผ!
ํ๋ณตํ ์ฝ๋ฉ ๋๊ธธ ๋ฐ๋ผ! ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.