์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ Go์–ธ์–ด ๋ฏธ๋“ค์›จ์–ด์™€ HTTP ํ•ธ๋“ค๋Ÿฌ ์ฒด์ธ์œผ๋กœ ๋งŒ๋“œ๋Š” ์š”์ฒญ ์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

๐Ÿš€ Go์–ธ์–ด ๋ฏธ๋“ค์›จ์–ด์™€ HTTP ํ•ธ๋“ค๋Ÿฌ ์ฒด์ธ์œผ๋กœ ๋งŒ๋“œ๋Š” ์š”์ฒญ ์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

์›น ์„œ๋ฒ„์˜ ์ˆจ์€ ์˜์›…, ๋ฏธ๋“ค์›จ์–ด์˜ ๋ชจ๋“  ๊ฒƒ์„ ํŒŒํ—ค์ณ๋ณด์ž!

๐ŸŽฏ ์›น ๊ฐœ๋ฐœ์„ ํ•˜๋‹ค ๋ณด๋ฉด ๋กœ๊น…, ์ธ์ฆ, ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ฐ™์€ ๊ณตํ†ต ๊ธฐ๋Šฅ๋“ค์„ ๋งค๋ฒˆ ๋ฐ˜๋ณตํ•ด์„œ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜์ง€? ์ด๋Ÿฐ ๋ฐ˜๋ณต ์ž‘์—…์„ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒŒ ๋ฐ”๋กœ ๋ฏธ๋“ค์›จ์–ด์•ผ. Go ์–ธ์–ด์—์„œ ๋ฏธ๋“ค์›จ์–ด๋Š” HTTP ์š”์ฒญ์ด ์ตœ์ข… ํ•ธ๋“ค๋Ÿฌ์— ๋„๋‹ฌํ•˜๊ธฐ ์ „์— ๊ฑฐ์น˜๋Š” ์ผ๋ จ์˜ ์ฒ˜๋ฆฌ ๋‹จ๊ณ„๋ฅผ ์˜๋ฏธํ•ด.

์˜ค๋Š˜์€ Go ์–ธ์–ด๋กœ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ , ์—ฌ๋Ÿฌ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ฒด์ธ์ฒ˜๋Ÿผ ์—ฐ๊ฒฐํ•ด์„œ ๊ฐ•๋ ฅํ•œ ์š”์ฒญ ์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์„ ์นœ๊ตฌ์—๊ฒŒ ์„ค๋ช…ํ•˜๋“ฏ ํŽธํ•˜๊ฒŒ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿ˜Š

๐Ÿ” ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ๋ญ”๋ฐ? ์™œ ํ•„์š”ํ•œ ๊ฑฐ์•ผ?

๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ดํ•ดํ•˜๋ ค๋ฉด ๋จผ์ € ์›น ์„œ๋ฒ„์—์„œ ์š”์ฒญ์ด ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€ ์•Œ์•„์•ผ ํ•ด. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด, ๊ทธ ์š”์ฒญ์€ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์ณ์„œ ์ตœ์ข…์ ์œผ๋กœ ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๊ฒŒ ๋ผ.

์˜ˆ๋ฅผ ๋“ค์–ด๋ณผ๊นŒ? ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€์— ์ ‘๊ทผํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ด:
1๏ธโƒฃ ๋จผ์ € ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ๋กœ๊ทธ๋ฅผ ๋‚จ๊ฒจ์•ผ ํ•˜๊ณ 
2๏ธโƒฃ ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•˜๊ณ 
3๏ธโƒฃ ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ์ฒดํฌํ•ด์•ผ ํ•˜๊ณ 
4๏ธโƒฃ ์‹ค์ œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๊ณ 
5๏ธโƒฃ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ ์ ˆํžˆ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ด

๐ŸŽญ ๋ฏธ๋“ค์›จ์–ด๋Š” ์ด๋Ÿฐ ๊ณตํ†ต ์ฒ˜๋ฆฌ ๋กœ์ง์„ ๋…๋ฆฝ์ ์ธ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌํ•œ ๊ฑฐ์•ผ. ๊ฐ ๋ฏธ๋“ค์›จ์–ด๋Š” ํ•˜๋‚˜์˜ ์ฑ…์ž„๋งŒ ๊ฐ€์ง€๊ณ , ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ ํ›„ ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด๋‚˜ ์ตœ์ข… ํ•ธ๋“ค๋Ÿฌ๋กœ ์ „๋‹ฌํ•˜๋Š” ๊ตฌ์กฐ์ง€.

์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ์‚ฌ์šฉ์ž ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ์ด๋Ÿฐ ๋ฏธ๋“ค์›จ์–ด ํŒจํ„ด์„ ํ™œ์šฉํ•˜๋ฉด ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ์—„์ฒญ๋‚˜๊ฒŒ ์ข‹์•„์ ธ! ๐Ÿ’ช

HTTP ์š”์ฒญ ์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ ๋ฐœ์†ก ๋กœ๊น… ๋ฏธ๋“ค์›จ์–ด ์ธ์ฆ ๋ฏธ๋“ค์›จ์–ด ๊ถŒํ•œ ์ฒดํฌ ๋ฏธ๋“ค์›จ์–ด ํ•ธ๋“ค๋Ÿฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‘๋‹ต ๋ฐ˜ํ™˜ ๊ฒฝ๋กœ ๊ฐ ๋ฏธ๋“ค์›จ์–ด์˜ ์—ญํ•  ๋กœ๊น…: ์š”์ฒญ/์‘๋‹ต ๊ธฐ๋ก ์ธ์ฆ: ์‚ฌ์šฉ์ž ์‹ ์› ํ™•์ธ ๊ถŒํ•œ: ์ ‘๊ทผ ๊ถŒํ•œ ๊ฒ€์ฆ ํ•ธ๋“ค๋Ÿฌ: ์‹ค์ œ ์ž‘์—… ์ˆ˜ํ–‰

๐Ÿ› ๏ธ 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))
}

โœจ ์™œ ์—ญ์ˆœ์œผ๋กœ ์ ์šฉํ• ๊นŒ?

๋ฏธ๋“ค์›จ์–ด๋Š” ์•ˆ์ชฝ๋ถ€ํ„ฐ ๋ฐ”๊นฅ์ชฝ์œผ๋กœ ๊ฐ์‹ธ๋Š” ๊ตฌ์กฐ์•ผ. ์‹คํ–‰ ์ˆœ์„œ๋Š” ๋ฐ”๊นฅ์ชฝ๋ถ€ํ„ฐ ์•ˆ์ชฝ์œผ๋กœ ์ง„ํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰๋˜๋ ค๋ฉด ์—ญ์ˆœ์œผ๋กœ ๊ฐ์‹ธ์•ผ ํ•ด!

๋ฏธ๋“ค์›จ์–ด ์ฒด์ธ ์‹คํ–‰ ์ˆœ์„œ 1. LoggingMiddleware 2. AuthMiddleware 3. CORSMiddleware ํ•ธ๋“ค๋Ÿฌ ์š”์ฒญ ํ๋ฆ„ ์š”์ฒญ ์ง„์ž… ์‘๋‹ต ๋ฐ˜ํ™˜

๋ฐฉ๋ฒ• 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] ์š”์ฒญ: GET /api/users
[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
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.