์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ Go์–ธ์–ด HTTP ํด๋ผ์ด์–ธํŠธ์™€ net/http ํŒจํ‚ค์ง€๋กœ ์™ธ๋ถ€ ์„œ๋น„์Šค ์™„๋ฒฝ ์—ฐ๋™ํ•˜๊ธฐ

๐Ÿš€ Go์–ธ์–ด HTTP ํด๋ผ์ด์–ธํŠธ์™€ net/http ํŒจํ‚ค์ง€๋กœ ์™ธ๋ถ€ ์„œ๋น„์Šค ์™„๋ฒฝ ์—ฐ๋™ํ•˜๊ธฐ

์นœ๊ตฌ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ๋ฐฐ์šฐ๋Š” Go์˜ HTTP ํ†ต์‹  ์™„์ „์ •๋ณต

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

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

Go Client ๐Ÿ–ฅ๏ธ net/http Package External API ๐ŸŒ REST API Server HTTP Request HTTP Response

๐ŸŽฏ HTTP ํด๋ผ์ด์–ธํŠธ์˜ ๊ธฐ๋ณธ ๊ฐœ๋…

๋จผ์ € HTTP ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ญ”์ง€๋ถ€ํ„ฐ ์งš๊ณ  ๋„˜์–ด๊ฐ€์ž. ๊ฐ„๋‹จํžˆ ๋งํ•˜๋ฉด, ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ ๋ฐ›๋Š” ํ”„๋กœ๊ทธ๋žจ์ด์•ผ. ์šฐ๋ฆฌ๊ฐ€ ์›น ๋ธŒ๋ผ์šฐ์ €๋กœ ์ธํ„ฐ๋„ท์„ ํ•  ๋•Œ๋„ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ HTTP ํด๋ผ์ด์–ธํŠธ ์—ญํ• ์„ ํ•˜๋Š” ๊ฑฐ์ง€. Go์—์„œ๋Š” ์ด๋Ÿฐ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ฝ”๋“œ๋กœ ์ง์ ‘ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด!

๐Ÿ’ก ์™œ HTTP ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ค‘์š”ํ• ๊นŒ?

โ€ข ์™ธ๋ถ€ API ์„œ๋น„์Šค ์—ฐ๋™ (๊ฒฐ์ œ, ์ธ์ฆ, ๋ฐ์ดํ„ฐ ์กฐํšŒ ๋“ฑ)
โ€ข ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ 
โ€ข ์›น ์Šคํฌ๋ž˜ํ•‘ ๋ฐ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘
โ€ข ์›นํ›…(Webhook) ์ „์†ก
โ€ข ์„œ๋“œํŒŒํ‹ฐ ์„œ๋น„์Šค์™€์˜ ํ†ตํ•ฉ

Go์˜ net/http ํŒจํ‚ค์ง€๋Š” 2009๋…„ Go ์–ธ์–ด๊ฐ€ ์ฒ˜์Œ ๊ณต๊ฐœ๋  ๋•Œ๋ถ€ํ„ฐ ํฌํ•จ๋œ ํ•ต์‹ฌ ํŒจํ‚ค์ง€์•ผ. ๊ตฌ๊ธ€ ์—”์ง€๋‹ˆ์–ด๋“ค์ด ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๊ฒ€์ฆ๋œ ์ฝ”๋“œ๋ผ์„œ ์•ˆ์ •์„ฑ๊ณผ ์„ฑ๋Šฅ์ด ์ •๋ง ๋›ฐ์–ด๋‚˜. ๐Ÿ”ฅ

๐Ÿš€ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ HTTP GET ์š”์ฒญ

์ž, ์ด์ œ ์‹ค์ „์œผ๋กœ ๋“ค์–ด๊ฐ€๋ณด์ž! ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ GET ์š”์ฒญ๋ถ€ํ„ฐ ์‹œ์ž‘ํ• ๊ฒŒ. GET ์š”์ฒญ์€ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ€์žฅ ํ”ํ•œ HTTP ๋ฉ”์„œ๋“œ์•ผ.

package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    // ๊ฐ„๋‹จํ•œ GET ์š”์ฒญ
    resp, err := http.Get("https://api.github.com/users/golang")
    if err != nil {
        fmt.Println("์š”์ฒญ ์‹คํŒจ:", err)
        return
    }
    defer resp.Body.Close() // ๋ฐ˜๋“œ์‹œ ๋‹ซ์•„์ค˜์•ผ ํ•ด!

    // ์‘๋‹ต ๋ณธ๋ฌธ ์ฝ๊ธฐ
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("์‘๋‹ต ์ฝ๊ธฐ ์‹คํŒจ:", err)
        return
    }

    fmt.Println("์ƒํƒœ ์ฝ”๋“œ:", resp.StatusCode)
    fmt.Println("์‘๋‹ต ๋‚ด์šฉ:", string(body))
}

๐ŸŽ“ ์ฝ”๋“œ ์„ค๋ช…

http.Get(): ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ GET ์š”์ฒญ ํ•จ์ˆ˜์•ผ. URL๋งŒ ๋„ฃ์œผ๋ฉด ๋!
defer resp.Body.Close(): ์ด๊ฑฐ ์ง„์งœ ์ค‘์š”ํ•ด. ์‘๋‹ต ๋ณธ๋ฌธ์„ ๋‹ซ์ง€ ์•Š์œผ๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด. defer๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•จ์ˆ˜๊ฐ€ ๋๋‚  ๋•Œ ์ž๋™์œผ๋กœ ๋‹ซ์•„์ค˜.
io.ReadAll(): ์‘๋‹ต ๋ณธ๋ฌธ ์ „์ฒด๋ฅผ ์ฝ์–ด์„œ ๋ฐ”์ดํŠธ ์Šฌ๋ผ์ด์Šค๋กœ ๋ฐ˜ํ™˜ํ•ด์ค˜.

์œ„ ์ฝ”๋“œ๋Š” GitHub API์—์„œ golang ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์˜ˆ์ œ์•ผ. ์‹คํ–‰ํ•˜๋ฉด JSON ํ˜•ํƒœ์˜ ์‘๋‹ต์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด. ์ •๋ง ๊ฐ„๋‹จํ•˜์ง€? ๐Ÿ˜Š

๐ŸŽจ ์ปค์Šคํ…€ HTTP ํด๋ผ์ด์–ธํŠธ ๋งŒ๋“ค๊ธฐ

http.Get()์€ ํŽธํ•˜์ง€๋งŒ, ์‹ค๋ฌด์—์„œ๋Š” ๋” ์„ธ๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•  ๋•Œ๊ฐ€ ๋งŽ์•„. ํƒ€์ž„์•„์›ƒ ์„ค์ •, ํ—ค๋” ์ถ”๊ฐ€, ์ฟ ํ‚ค ๊ด€๋ฆฌ ๋“ฑ๋“ฑ... ์ด๋Ÿด ๋•Œ๋Š” ์ปค์Šคํ…€ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•ด!

package main

import (
    "fmt"
    "io"
    "net/http"
    "time"
)

func main() {
    // ์ปค์Šคํ…€ ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
    client := &http.Client{
        Timeout: 10 * time.Second, // 10์ดˆ ํƒ€์ž„์•„์›ƒ
    }

    // ์š”์ฒญ ๊ฐ์ฒด ์ƒ์„ฑ
    req, err := http.NewRequest("GET", "https://api.github.com/users/golang", nil)
    if err != nil {
        fmt.Println("์š”์ฒญ ์ƒ์„ฑ ์‹คํŒจ:", err)
        return
    }

    // ํ—ค๋” ์ถ”๊ฐ€
    req.Header.Add("User-Agent", "My-Go-Client/1.0")
    req.Header.Add("Accept", "application/json")

    // ์š”์ฒญ ์‹คํ–‰
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("์š”์ฒญ ์‹คํŒจ:", err)
        return
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println("์‘๋‹ต:", string(body))
}

๐Ÿ”ง ์ปค์Šคํ…€ ํด๋ผ์ด์–ธํŠธ์˜ ์ฃผ์š” ์„ค์ •๋“ค

์„ค์ • ์„ค๋ช… ์˜ˆ์‹œ
Timeout ์ „์ฒด ์š”์ฒญ์˜ ์ตœ๋Œ€ ๋Œ€๊ธฐ ์‹œ๊ฐ„ 30 * time.Second
Transport ์ €์ˆ˜์ค€ HTTP ์ „์†ก ์„ค์ • ์—ฐ๊ฒฐ ํ’€, ํ”„๋ก์‹œ ๋“ฑ
CheckRedirect ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฒ˜๋ฆฌ ๋ฐฉ์‹ ์ž๋™ ์ถ”์  ์—ฌ๋ถ€ ๊ฒฐ์ •
Jar ์ฟ ํ‚ค ์ €์žฅ์†Œ ์„ธ์…˜ ์œ ์ง€์šฉ

ํƒ€์ž„์•„์›ƒ ์„ค์ •์€ ์ •๋ง ์ค‘์š”ํ•ด! ์™ธ๋ถ€ ์„œ๋น„์Šค๊ฐ€ ์‘๋‹ตํ•˜์ง€ ์•Š์„ ๋•Œ ๋ฌดํ•œ์ • ๊ธฐ๋‹ค๋ฆฌ๋ฉด ์•ˆ ๋˜๊ฑฐ๋“ . ๋ณดํ†ต ์‹ค๋ฌด์—์„œ๋Š” 10~30์ดˆ ์ •๋„๋กœ ์„ค์ •ํ•˜๋Š” ํŽธ์ด์•ผ. โฑ๏ธ

๐Ÿ“ฎ POST ์š”์ฒญ์œผ๋กœ ๋ฐ์ดํ„ฐ ์ „์†กํ•˜๊ธฐ

GET์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฑฐ๋ผ๋ฉด, POST๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„๋กœ ๋ณด๋‚ผ ๋•Œ ์‚ฌ์šฉํ•ด. ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ, ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ ๋“ฑ ๋Œ€๋ถ€๋ถ„์˜ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ์ž‘์—…์ด POST๋กœ ์ด๋ฃจ์–ด์ ธ.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
    Age   int    `json:"age"`
}

func main() {
    // ์ „์†กํ•  ๋ฐ์ดํ„ฐ ์ค€๋น„
    user := User{
        Name:  "ํ™๊ธธ๋™",
        Email: "hong@example.com",
        Age:   25,
    }

    // JSON์œผ๋กœ ์ธ์ฝ”๋”ฉ
    jsonData, err := json.Marshal(user)
    if err != nil {
        fmt.Println("JSON ์ธ์ฝ”๋”ฉ ์‹คํŒจ:", err)
        return
    }

    // POST ์š”์ฒญ ์ „์†ก
    resp, err := http.Post(
        "https://jsonplaceholder.typicode.com/users",
        "application/json",
        bytes.NewBuffer(jsonData),
    )
    if err != nil {
        fmt.Println("์š”์ฒญ ์‹คํŒจ:", err)
        return
    }
    defer resp.Body.Close()

    // ์‘๋‹ต ํ™•์ธ
    body, _ := io.ReadAll(resp.Body)
    fmt.Println("์ƒํƒœ ์ฝ”๋“œ:", resp.StatusCode)
    fmt.Println("์‘๋‹ต:", string(body))
}

๐ŸŽฏ POST ์š”์ฒญ์˜ ํ•ต์‹ฌ ํฌ์ธํŠธ

1. Content-Type ํ—ค๋”: ๋ณด๋‚ด๋Š” ๋ฐ์ดํ„ฐ ํ˜•์‹์„ ๋ช…์‹œํ•ด์•ผ ํ•ด. JSON์ด๋ฉด "application/json", ํผ ๋ฐ์ดํ„ฐ๋ฉด "application/x-www-form-urlencoded"

2. ์š”์ฒญ ๋ณธ๋ฌธ(Body): io.Reader ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•ด. bytes.NewBuffer()๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•ด.

3. ์—๋Ÿฌ ์ฒ˜๋ฆฌ: ๋„คํŠธ์›Œํฌ ์š”์ฒญ์€ ์–ธ์ œ๋“  ์‹คํŒจํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ๋ฐ˜๋“œ์‹œ ์—๋Ÿฌ ์ฒดํฌ๋ฅผ ํ•ด์ค˜์•ผ ํ•ด!

๐Ÿ” ์ธ์ฆ์ด ํ•„์š”ํ•œ API ํ˜ธ์ถœํ•˜๊ธฐ

์‹ค๋ฌด์—์„œ ๋งŒ๋‚˜๋Š” ๋Œ€๋ถ€๋ถ„์˜ API๋Š” ์ธ์ฆ์ด ํ•„์š”ํ•ด. API ํ‚ค, Bearer ํ† ํฐ, Basic Auth ๋“ฑ ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์ด ์žˆ๋Š”๋ฐ, ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด์ž!

package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    // 1. API ํ‚ค๋ฅผ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ
    apiKeyURL := "https://api.example.com/data?api_key=YOUR_API_KEY"
    
    // 2. Bearer ํ† ํฐ ์ธ์ฆ
    client := &http.Client{}
    req, _ := http.NewRequest("GET", "https://api.example.com/protected", nil)
    req.Header.Add("Authorization", "Bearer YOUR_ACCESS_TOKEN")
    
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("์š”์ฒญ ์‹คํŒจ:", err)
        return
    }
    defer resp.Body.Close()

    // 3. Basic Authentication
    req2, _ := http.NewRequest("GET", "https://api.example.com/basic", nil)
    req2.SetBasicAuth("username", "password")
    
    resp2, err := client.Do(req2)
    if err != nil {
        fmt.Println("์š”์ฒญ ์‹คํŒจ:", err)
        return
    }
    defer resp2.Body.Close()

    body, _ := io.ReadAll(resp2.Body)
    fmt.Println("์‘๋‹ต:", string(body))
}

โš ๏ธ ๋ณด์•ˆ ์ฃผ์˜์‚ฌํ•ญ

API ํ‚ค๋‚˜ ํ† ํฐ์„ ์ฝ”๋“œ์— ์ง์ ‘ ํ•˜๋“œ์ฝ”๋”ฉํ•˜๋ฉด ์ ˆ๋Œ€ ์•ˆ ๋ผ! ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋‚˜ ์„ค์ • ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ด์„œ ๊ด€๋ฆฌํ•ด์•ผ ํ•ด. ํŠนํžˆ GitHub ๊ฐ™์€ ๊ณต๊ฐœ ์ €์žฅ์†Œ์— ์˜ฌ๋ฆด ๋•Œ๋Š” ๋”์šฑ ์กฐ์‹ฌํ•ด์•ผ ํ•ด. ์‹ค์ˆ˜๋กœ ์˜ฌ๋ ธ๋‹ค๊ฐ€ ํ•ดํ‚น๋‹นํ•˜๋Š” ์‚ฌ๋ก€๊ฐ€ ์ •๋ง ๋งŽ๊ฑฐ๋“ . ๐Ÿ˜ฑ

ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ผ:

import "os"

func main() {
    apiKey := os.Getenv("API_KEY")
    if apiKey == "" {
        fmt.Println("API_KEY ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค")
        return
    }
    
    // apiKey ์‚ฌ์šฉ...
}

โšก ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ: Transport์™€ ์—ฐ๊ฒฐ ํ’€ ๊ด€๋ฆฌ

์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ๋Š” ์ข€ ๋” ๊นŠ์ด ๋“ค์–ด๊ฐ€๋ณผ๊ฒŒ. http.Transport๋Š” ์‹ค์ œ HTTP ํ†ต์‹ ์„ ๋‹ด๋‹นํ•˜๋Š” ์ €์ˆ˜์ค€ ์ปดํฌ๋„ŒํŠธ์•ผ. ์—ฐ๊ฒฐ ์žฌ์‚ฌ์šฉ, ํƒ€์ž„์•„์›ƒ ์„ธ๋ถ€ ์„ค์ •, ํ”„๋ก์‹œ ๋“ฑ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด.

package main

import (
    "fmt"
    "net"
    "net/http"
    "time"
)

func main() {
    // ์ปค์Šคํ…€ Transport ์„ค์ •
    transport := &http.Transport{
        // ์—ฐ๊ฒฐ ํ’€ ์„ค์ •
        MaxIdleConns:        100,              // ์ „์ฒด ์œ ํœด ์—ฐ๊ฒฐ ์ˆ˜
        MaxIdleConnsPerHost: 10,               // ํ˜ธ์ŠคํŠธ๋‹น ์œ ํœด ์—ฐ๊ฒฐ ์ˆ˜
        MaxConnsPerHost:     100,              // ํ˜ธ์ŠคํŠธ๋‹น ์ตœ๋Œ€ ์—ฐ๊ฒฐ ์ˆ˜
        
        // ํƒ€์ž„์•„์›ƒ ์„ธ๋ถ€ ์„ค์ •
        DialContext: (&net.Dialer{
            Timeout:   5 * time.Second,  // ์—ฐ๊ฒฐ ํƒ€์ž„์•„์›ƒ
            KeepAlive: 30 * time.Second, // Keep-Alive ์ฃผ๊ธฐ
        }).DialContext,
        
        TLSHandshakeTimeout:   10 * time.Second,
        ResponseHeaderTimeout: 10 * time.Second,
        ExpectContinueTimeout: 1 * time.Second,
        
        // ์œ ํœด ์—ฐ๊ฒฐ ์ •๋ฆฌ ์ฃผ๊ธฐ
        IdleConnTimeout: 90 * time.Second,
    }

    client := &http.Client{
        Transport: transport,
        Timeout:   30 * time.Second,
    }

    // ์ด ํด๋ผ์ด์–ธํŠธ๋กœ ์—ฌ๋Ÿฌ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด
    // ์—ฐ๊ฒฐ์ด ์žฌ์‚ฌ์šฉ๋˜์–ด ์„ฑ๋Šฅ์ด ํ–ฅ์ƒ๋ผ!
    for i := 0; i < 10; i++ {
        resp, err := client.Get("https://api.github.com/users/golang")
        if err != nil {
            fmt.Println("์š”์ฒญ ์‹คํŒจ:", err)
            continue
        }
        fmt.Println("์š”์ฒญ", i+1, "์™„๋ฃŒ:", resp.StatusCode)
        resp.Body.Close()
    }
}

๐ŸŽฏ Transport ์„ค์ • ์ตœ์ ํ™” ๊ฐ€์ด๋“œ

์—ฐ๊ฒฐ ํ’€ ํฌ๊ธฐ ๊ฒฐ์ •

โ€ข ์†Œ๊ทœ๋ชจ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜: MaxIdleConnsPerHost 2~5๊ฐœ
โ€ข ์ค‘๊ทœ๋ชจ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜: MaxIdleConnsPerHost 10~20๊ฐœ
โ€ข ๋Œ€๊ทœ๋ชจ ํŠธ๋ž˜ํ”ฝ: MaxIdleConnsPerHost 50~100๊ฐœ
โ€ข ๋„ˆ๋ฌด ๋งŽ์ด ์„ค์ •ํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋‚ญ๋น„, ๋„ˆ๋ฌด ์ ์œผ๋ฉด ์—ฐ๊ฒฐ ์žฌ์ƒ์„ฑ ์˜ค๋ฒ„ํ—ค๋“œ ๋ฐœ์ƒ

ํƒ€์ž„์•„์›ƒ ์„ค์ • ์ „๋žต

โ€ข DialTimeout: ์—ฐ๊ฒฐ ์ˆ˜๋ฆฝ ์‹œ๊ฐ„ (๋ณดํ†ต 5~10์ดˆ)
โ€ข TLSHandshakeTimeout: SSL/TLS ํ•ธ๋“œ์…ฐ์ดํฌ (๋ณดํ†ต 10์ดˆ)
โ€ข ResponseHeaderTimeout: ํ—ค๋” ์ˆ˜์‹  ๋Œ€๊ธฐ (๋ณดํ†ต 10~30์ดˆ)
โ€ข Client.Timeout: ์ „์ฒด ์š”์ฒญ ์‹œ๊ฐ„ (๋ณดํ†ต 30~60์ดˆ)

์—ฐ๊ฒฐ ํ’€์„ ์ œ๋Œ€๋กœ ์„ค์ •ํ•˜๋ฉด ์„ฑ๋Šฅ์ด ์—„์ฒญ๋‚˜๊ฒŒ ํ–ฅ์ƒ๋ผ. ๊ฐ™์€ ์„œ๋ฒ„๋กœ ๋ฐ˜๋ณต ์š”์ฒญํ•  ๋•Œ TCP ์—ฐ๊ฒฐ์„ ์žฌ์‚ฌ์šฉํ•˜๋‹ˆ๊นŒ ํ•ธ๋“œ์…ฐ์ดํฌ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์—†์–ด์ง€๊ฑฐ๋“ ! ๐Ÿš€

๐Ÿ”„ ์ปจํ…์ŠคํŠธ๋ฅผ ํ™œ์šฉํ•œ ์š”์ฒญ ์ทจ์†Œ

Go์˜ context ํŒจํ‚ค์ง€๋Š” ์ •๋ง ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์ด์•ผ. ์š”์ฒญ ์ทจ์†Œ, ํƒ€์ž„์•„์›ƒ, ๊ฐ’ ์ „๋‹ฌ ๋“ฑ์„ ์šฐ์•„ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ ๊ธด ์ž‘์—…์„ ์ค‘๊ฐ„์— ์ทจ์†Œํ•ด์•ผ ํ•  ๋•Œ ์œ ์šฉํ•ด!

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "time"
)

func main() {
    // 5์ดˆ ํƒ€์ž„์•„์›ƒ ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    // ์ปจํ…์ŠคํŠธ๋ฅผ ํฌํ•จํ•œ ์š”์ฒญ ์ƒ์„ฑ
    req, err := http.NewRequestWithContext(
        ctx,
        "GET",
        "https://httpbin.org/delay/10", // 10์ดˆ ๊ฑธ๋ฆฌ๋Š” API
        nil,
    )
    if err != nil {
        fmt.Println("์š”์ฒญ ์ƒ์„ฑ ์‹คํŒจ:", err)
        return
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        // ํƒ€์ž„์•„์›ƒ์ด๋‚˜ ์ทจ์†Œ ์‹œ ์—๋Ÿฌ ๋ฐœ์ƒ
        fmt.Println("์š”์ฒญ ์‹คํŒจ:", err)
        return
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println("์‘๋‹ต:", string(body))
}

// ์ˆ˜๋™ ์ทจ์†Œ ์˜ˆ์ œ
func manualCancelExample() {
    ctx, cancel := context.WithCancel(context.Background())
    
    // 3์ดˆ ํ›„ ์ทจ์†Œ
    go func() {
        time.Sleep(3 * time.Second)
        cancel()
        fmt.Println("์š”์ฒญ ์ทจ์†Œ!")
    }()

    req, _ := http.NewRequestWithContext(ctx, "GET", "https://httpbin.org/delay/10", nil)
    
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("์š”์ฒญ์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค:", err)
        return
    }
    defer resp.Body.Close()
}

๐Ÿ’ก ์ปจํ…์ŠคํŠธ ์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค

1. ์‚ฌ์šฉ์ž ์š”์ฒญ ์ทจ์†Œ: ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ๋– ๋‚  ๋•Œ
2. ํƒ€์ž„์•„์›ƒ ์ œ์–ด: ํŠน์ • ์ž‘์—…์— ์‹œ๊ฐ„ ์ œํ•œ์„ ๋‘๊ณ  ์‹ถ์„ ๋•Œ
3. ์—ฐ์‡„ ์ž‘์—… ์ทจ์†Œ: ํ•˜๋‚˜์˜ ์ž‘์—…์ด ์‹คํŒจํ•˜๋ฉด ๊ด€๋ จ๋œ ๋ชจ๋“  ์ž‘์—…์„ ์ทจ์†Œํ•  ๋•Œ
4. ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ: ๊ณ ๋ฃจํ‹ด์ด ์ข…๋ฃŒ๋  ๋•Œ ๊ด€๋ จ ๋ฆฌ์†Œ์Šค๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌํ•  ๋•Œ

๐ŸŽญ ์‹ค์ „ ์˜ˆ์ œ: JSON API ํด๋ผ์ด์–ธํŠธ ๋งŒ๋“ค๊ธฐ

์ด์ œ ๋ฐฐ์šด ๊ฑธ ์ข…ํ•ฉํ•ด์„œ ์‹ค์šฉ์ ์ธ JSON API ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž! ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ์™ธ๋ถ€ ๊ฒฐ์ œ API๋‚˜ ์•Œ๋ฆผ ์„œ๋น„์Šค๋ฅผ ์—ฐ๋™ํ•  ๋•Œ ์ด๋Ÿฐ ํŒจํ„ด์„ ๋งŽ์ด ์‚ฌ์šฉํ•ด. ๐Ÿ˜Š

package main

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

// API ํด๋ผ์ด์–ธํŠธ ๊ตฌ์กฐ์ฒด
type APIClient struct {
    BaseURL    string
    HTTPClient *http.Client
    APIKey     string
}

// ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ์ž
func NewAPIClient(baseURL, apiKey string) *APIClient {
    return &APIClient{
        BaseURL: baseURL,
        APIKey:  apiKey,
        HTTPClient: &http.Client{
            Timeout: 30 * time.Second,
            Transport: &http.Transport{
                MaxIdleConnsPerHost: 10,
                IdleConnTimeout:     90 * time.Second,
            },
        },
    }
}

// GET ์š”์ฒญ ํ—ฌํผ ๋ฉ”์„œ๋“œ
func (c *APIClient) Get(ctx context.Context, endpoint string, result interface{}) error {
    url := c.BaseURL + endpoint
    
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return fmt.Errorf("์š”์ฒญ ์ƒ์„ฑ ์‹คํŒจ: %w", err)
    }

    // ๊ณตํ†ต ํ—ค๋” ์„ค์ •
    req.Header.Set("Authorization", "Bearer "+c.APIKey)
    req.Header.Set("Accept", "application/json")

    resp, err := c.HTTPClient.Do(req)
    if err != nil {
        return fmt.Errorf("์š”์ฒญ ์‹คํŒจ: %w", err)
    }
    defer resp.Body.Close()

    // ์ƒํƒœ ์ฝ”๋“œ ํ™•์ธ
    if resp.StatusCode != http.StatusOK {
        body, _ := io.ReadAll(resp.Body)
        return fmt.Errorf("API ์—๋Ÿฌ (์ฝ”๋“œ: %d): %s", resp.StatusCode, string(body))
    }

    // JSON ๋””์ฝ”๋”ฉ
    if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
        return fmt.Errorf("JSON ๋””์ฝ”๋”ฉ ์‹คํŒจ: %w", err)
    }

    return nil
}

// POST ์š”์ฒญ ํ—ฌํผ ๋ฉ”์„œ๋“œ
func (c *APIClient) Post(ctx context.Context, endpoint string, payload, result interface{}) error {
    url := c.BaseURL + endpoint

    // ํŽ˜์ด๋กœ๋“œ๋ฅผ JSON์œผ๋กœ ์ธ์ฝ”๋”ฉ
    jsonData, err := json.Marshal(payload)
    if err != nil {
        return fmt.Errorf("JSON ์ธ์ฝ”๋”ฉ ์‹คํŒจ: %w", err)
    }

    req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
    if err != nil {
        return fmt.Errorf("์š”์ฒญ ์ƒ์„ฑ ์‹คํŒจ: %w", err)
    }

    // ๊ณตํ†ต ํ—ค๋” ์„ค์ •
    req.Header.Set("Authorization", "Bearer "+c.APIKey)
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Accept", "application/json")

    resp, err := c.HTTPClient.Do(req)
    if err != nil {
        return fmt.Errorf("์š”์ฒญ ์‹คํŒจ: %w", err)
    }
    defer resp.Body.Close()

    // ์ƒํƒœ ์ฝ”๋“œ ํ™•์ธ (200, 201 ๋ชจ๋‘ ์„ฑ๊ณต)
    if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
        body, _ := io.ReadAll(resp.Body)
        return fmt.Errorf("API ์—๋Ÿฌ (์ฝ”๋“œ: %d): %s", resp.StatusCode, string(body))
    }

    // ์‘๋‹ต์ด ์žˆ์œผ๋ฉด ๋””์ฝ”๋”ฉ
    if result != nil {
        if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
            return fmt.Errorf("JSON ๋””์ฝ”๋”ฉ ์‹คํŒจ: %w", err)
        }
    }

    return nil
}

// ์‚ฌ์šฉ ์˜ˆ์ œ
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func main() {
    // ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
    client := NewAPIClient("https://api.example.com", "your-api-key")

    // GET ์š”์ฒญ ์˜ˆ์ œ
    ctx := context.Background()
    var user User
    err := client.Get(ctx, "/users/1", &user)
    if err != nil {
        fmt.Println("์‚ฌ์šฉ์ž ์กฐํšŒ ์‹คํŒจ:", err)
        return
    }
    fmt.Printf("์‚ฌ์šฉ์ž ์ •๋ณด: %+v\n", user)

    // POST ์š”์ฒญ ์˜ˆ์ œ
    newUser := User{
        Name:  "๊น€๊ฐœ๋ฐœ",
        Email: "kim@example.com",
    }
    var createdUser User
    err = client.Post(ctx, "/users", newUser, &createdUser)
    if err != nil {
        fmt.Println("์‚ฌ์šฉ์ž ์ƒ์„ฑ ์‹คํŒจ:", err)
        return
    }
    fmt.Printf("์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž: %+v\n", createdUser)
}

๐ŸŽจ ์ด ํด๋ผ์ด์–ธํŠธ์˜ ์žฅ์ ๋“ค

์žฌ์‚ฌ์šฉ์„ฑ: ํ•œ ๋ฒˆ ๋งŒ๋“ค์–ด๋‘๋ฉด ์—ฌ๋Ÿฌ ์—”๋“œํฌ์ธํŠธ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
์ผ๊ด€์„ฑ: ๋ชจ๋“  ์š”์ฒญ์— ๋™์ผํ•œ ํ—ค๋”์™€ ์„ค์ • ์ ์šฉ
์—๋Ÿฌ ์ฒ˜๋ฆฌ: ์ฒด๊ณ„์ ์ธ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์œผ๋กœ ๋””๋ฒ„๊น… ์šฉ์ด
ํƒ€์ž… ์•ˆ์ •์„ฑ: ์ œ๋„ค๋ฆญ ๋Œ€์‹  interface{}๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ ํƒ€์ž… ๋ณ€ํ™˜์€ ์ž๋™
์ปจํ…์ŠคํŠธ ์ง€์›: ์ทจ์†Œ์™€ ํƒ€์ž„์•„์›ƒ์„ ์šฐ์•„ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ

๐Ÿ” ์‘๋‹ต ์ฒ˜๋ฆฌ์™€ ์—๋Ÿฌ ํ•ธ๋“ค๋ง ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

HTTP ํ†ต์‹ ์—์„œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋Š” ์ •๋ง ์ค‘์š”ํ•ด. ๋„คํŠธ์›Œํฌ๋Š” ์–ธ์ œ๋“  ๋ถˆ์•ˆ์ •ํ•  ์ˆ˜ ์žˆ๊ณ , ์„œ๋ฒ„๋„ ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ์ œ๋Œ€๋กœ ๋œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์—†์ด๋Š” ํ”„๋กœ๋•์…˜์—์„œ ํฐ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์–ด! ๐Ÿ˜ฐ

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

// API ์—๋Ÿฌ ์‘๋‹ต ๊ตฌ์กฐ์ฒด
type APIError struct {
    StatusCode int    `json:"status_code"`
    Message    string `json:"message"`
    ErrorCode  string `json:"error_code"`
}

func (e *APIError) Error() string {
    return fmt.Sprintf("API ์—๋Ÿฌ [%d] %s: %s", e.StatusCode, e.ErrorCode, e.Message)
}

// ์žฌ์‹œ๋„ ๋กœ์ง์„ ํฌํ•จํ•œ ์š”์ฒญ ํ•จ์ˆ˜
func requestWithRetry(url string, maxRetries int) (*http.Response, error) {
    var resp *http.Response
    var err error

    client := &http.Client{
        Timeout: 10 * time.Second,
    }

    for i := 0; i < maxRetries; i++ {
        resp, err = client.Get(url)
        
        // ์„ฑ๊ณตํ•˜๋ฉด ๋ฐ”๋กœ ๋ฐ˜ํ™˜
        if err == nil && resp.StatusCode == http.StatusOK {
            return resp, nil
        }

        // ์‹คํŒจ ์‹œ ๋กœ๊ทธ ์ถœ๋ ฅ
        if err != nil {
            fmt.Printf("์‹œ๋„ %d/%d ์‹คํŒจ: %v\n", i+1, maxRetries, err)
        } else {
            fmt.Printf("์‹œ๋„ %d/%d ์‹คํŒจ: ์ƒํƒœ ์ฝ”๋“œ %d\n", i+1, maxRetries, resp.StatusCode)
            resp.Body.Close()
        }

        // ๋งˆ์ง€๋ง‰ ์‹œ๋„๊ฐ€ ์•„๋‹ˆ๋ฉด ๋Œ€๊ธฐ
        if i < maxRetries-1 {
            waitTime := time.Duration(i+1) * time.Second
            fmt.Printf("%v ํ›„ ์žฌ์‹œ๋„...\n", waitTime)
            time.Sleep(waitTime)
        }
    }

    if err != nil {
        return nil, fmt.Errorf("์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜ ์ดˆ๊ณผ: %w", err)
    }
    return resp, fmt.Errorf("์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜ ์ดˆ๊ณผ: ์ƒํƒœ ์ฝ”๋“œ %d", resp.StatusCode)
}

// ์ƒํƒœ ์ฝ”๋“œ๋ณ„ ์ฒ˜๋ฆฌ
func handleResponse(resp *http.Response) error {
    defer resp.Body.Close()

    switch resp.StatusCode {
    case http.StatusOK, http.StatusCreated:
        // ์„ฑ๊ณต
        return nil
        
    case http.StatusBadRequest:
        // ์ž˜๋ชป๋œ ์š”์ฒญ
        body, _ := io.ReadAll(resp.Body)
        return &APIError{
            StatusCode: resp.StatusCode,
            Message:    string(body),
            ErrorCode:  "BAD_REQUEST",
        }
        
    case http.StatusUnauthorized:
        // ์ธ์ฆ ์‹คํŒจ
        return &APIError{
            StatusCode: resp.StatusCode,
            Message:    "์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
            ErrorCode:  "UNAUTHORIZED",
        }
        
    case http.StatusForbidden:
        // ๊ถŒํ•œ ์—†์Œ
        return &APIError{
            StatusCode: resp.StatusCode,
            Message:    "์ ‘๊ทผ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค",
            ErrorCode:  "FORBIDDEN",
        }
        
    case http.StatusNotFound:
        // ๋ฆฌ์†Œ์Šค ์—†์Œ
        return &APIError{
            StatusCode: resp.StatusCode,
            Message:    "์š”์ฒญํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
            ErrorCode:  "NOT_FOUND",
        }
        
    case http.StatusTooManyRequests:
        // ์š”์ฒญ ์ œํ•œ ์ดˆ๊ณผ
        retryAfter := resp.Header.Get("Retry-After")
        return &APIError{
            StatusCode: resp.StatusCode,
            Message:    fmt.Sprintf("์š”์ฒญ ์ œํ•œ ์ดˆ๊ณผ. %s ํ›„ ์žฌ์‹œ๋„ํ•˜์„ธ์š”", retryAfter),
            ErrorCode:  "RATE_LIMIT",
        }
        
    case http.StatusInternalServerError:
        // ์„œ๋ฒ„ ์—๋Ÿฌ
        return &APIError{
            StatusCode: resp.StatusCode,
            Message:    "์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค",
            ErrorCode:  "INTERNAL_ERROR",
        }
        
    default:
        // ๊ธฐํƒ€ ์—๋Ÿฌ
        body, _ := io.ReadAll(resp.Body)
        return &APIError{
            StatusCode: resp.StatusCode,
            Message:    string(body),
            ErrorCode:  "UNKNOWN_ERROR",
        }
    }
}

func main() {
    // ์žฌ์‹œ๋„ ๋กœ์ง ์‚ฌ์šฉ ์˜ˆ์ œ
    resp, err := requestWithRetry("https://api.github.com/users/golang", 3)
    if err != nil {
        fmt.Println("์š”์ฒญ ์‹คํŒจ:", err)
        return
    }

    // ์‘๋‹ต ์ฒ˜๋ฆฌ
    if err := handleResponse(resp); err != nil {
        fmt.Println("์‘๋‹ต ์ฒ˜๋ฆฌ ์‹คํŒจ:", err)
        return
    }

    fmt.Println("์š”์ฒญ ์„ฑ๊ณต!")
}

๐ŸŽฏ HTTP ์ƒํƒœ ์ฝ”๋“œ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

๋ฒ”์œ„ ์˜๋ฏธ ์ฃผ์š” ์ฝ”๋“œ
2xx ์„ฑ๊ณต 200 OK, 201 Created, 204 No Content
3xx ๋ฆฌ๋‹ค์ด๋ ‰์…˜ 301 Moved Permanently, 302 Found, 304 Not Modified
4xx ํด๋ผ์ด์–ธํŠธ ์—๋Ÿฌ 400 Bad Request, 401 Unauthorized, 404 Not Found
5xx ์„œ๋ฒ„ ์—๋Ÿฌ 500 Internal Server Error, 503 Service Unavailable

๐ŸŽช ๋ฉ€ํ‹ฐํŒŒํŠธ ํผ ๋ฐ์ดํ„ฐ์™€ ํŒŒ์ผ ์—…๋กœ๋“œ

ํŒŒ์ผ ์—…๋กœ๋“œ๋Š” ์กฐ๊ธˆ ํŠน๋ณ„ํ•ด. JSON์ด๋‚˜ ์ผ๋ฐ˜ ํ…์ŠคํŠธ์™€ ๋‹ฌ๋ฆฌ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•ด์•ผ ํ•˜๊ฑฐ๋“ . Go์˜ multipart ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด!

package main

import (
    "bytes"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "os"
    "path/filepath"
)

// ํŒŒ์ผ ์—…๋กœ๋“œ ํ•จ์ˆ˜
func uploadFile(url, fieldName, filePath string) error {
    // ํŒŒ์ผ ์—ด๊ธฐ
    file, err := os.Open(filePath)
    if err != nil {
        return fmt.Errorf("ํŒŒ์ผ ์—ด๊ธฐ ์‹คํŒจ: %w", err)
    }
    defer file.Close()

    // ๋ฉ€ํ‹ฐํŒŒํŠธ ํผ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)

    // ํŒŒ์ผ ํ•„๋“œ ์ถ”๊ฐ€
    part, err := writer.CreateFormFile(fieldName, filepath.Base(filePath))
    if err != nil {
        return fmt.Errorf("ํผ ํŒŒ์ผ ์ƒ์„ฑ ์‹คํŒจ: %w", err)
    }

    // ํŒŒ์ผ ๋‚ด์šฉ ๋ณต์‚ฌ
    if _, err := io.Copy(part, file); err != nil {
        return fmt.Errorf("ํŒŒ์ผ ๋ณต์‚ฌ ์‹คํŒจ: %w", err)
    }

    // ์ถ”๊ฐ€ ํ•„๋“œ (์„ ํƒ์‚ฌํ•ญ)
    writer.WriteField("description", "์—…๋กœ๋“œ๋œ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค")
    writer.WriteField("category", "documents")

    // Writer ๋‹ซ๊ธฐ (์ค‘์š”!)
    if err := writer.Close(); err != nil {
        return fmt.Errorf("writer ๋‹ซ๊ธฐ ์‹คํŒจ: %w", err)
    }

    // HTTP ์š”์ฒญ ์ƒ์„ฑ
    req, err := http.NewRequest("POST", url, body)
    if err != nil {
        return fmt.Errorf("์š”์ฒญ ์ƒ์„ฑ ์‹คํŒจ: %w", err)
    }

    // Content-Type ํ—ค๋” ์„ค์ • (boundary ํฌํ•จ)
    req.Header.Set("Content-Type", writer.FormDataContentType())

    // ์š”์ฒญ ์ „์†ก
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return fmt.Errorf("์š”์ฒญ ์‹คํŒจ: %w", err)
    }
    defer resp.Body.Close()

    // ์‘๋‹ต ํ™•์ธ
    if resp.StatusCode != http.StatusOK {
        bodyBytes, _ := io.ReadAll(resp.Body)
        return fmt.Errorf("์—…๋กœ๋“œ ์‹คํŒจ (์ฝ”๋“œ: %d): %s", resp.StatusCode, string(bodyBytes))
    }

    fmt.Println("ํŒŒ์ผ ์—…๋กœ๋“œ ์„ฑ๊ณต!")
    return nil
}

// ์—ฌ๋Ÿฌ ํŒŒ์ผ ๋™์‹œ ์—…๋กœ๋“œ
func uploadMultipleFiles(url string, files map[string]string) error {
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)

    // ๊ฐ ํŒŒ์ผ ์ถ”๊ฐ€
    for fieldName, filePath := range files {
        file, err := os.Open(filePath)
        if err != nil {
            return fmt.Errorf("ํŒŒ์ผ ์—ด๊ธฐ ์‹คํŒจ (%s): %w", filePath, err)
        }

        part, err := writer.CreateFormFile(fieldName, filepath.Base(filePath))
        if err != nil {
            file.Close()
            return fmt.Errorf("ํผ ํŒŒ์ผ ์ƒ์„ฑ ์‹คํŒจ: %w", err)
        }

        _, err = io.Copy(part, file)
        file.Close()
        if err != nil {
            return fmt.Errorf("ํŒŒ์ผ ๋ณต์‚ฌ ์‹คํŒจ: %w", err)
        }
    }

    writer.Close()

    req, err := http.NewRequest("POST", url, body)
    if err != nil {
        return fmt.Errorf("์š”์ฒญ ์ƒ์„ฑ ์‹คํŒจ: %w", err)
    }

    req.Header.Set("Content-Type", writer.FormDataContentType())

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return fmt.Errorf("์š”์ฒญ ์‹คํŒจ: %w", err)
    }
    defer resp.Body.Close()

    fmt.Println("๋ชจ๋“  ํŒŒ์ผ ์—…๋กœ๋“œ ์™„๋ฃŒ!")
    return nil
}

func main() {
    // ๋‹จ์ผ ํŒŒ์ผ ์—…๋กœ๋“œ
    err := uploadFile(
        "https://example.com/upload",
        "file",
        "/path/to/document.pdf",
    )
    if err != nil {
        fmt.Println("์—…๋กœ๋“œ ์‹คํŒจ:", err)
        return
    }

    // ์—ฌ๋Ÿฌ ํŒŒ์ผ ์—…๋กœ๋“œ
    files := map[string]string{
        "document": "/path/to/document.pdf",
        "image":    "/path/to/image.jpg",
        "video":    "/path/to/video.mp4",
    }
    err = uploadMultipleFiles("https://example.com/upload-multiple", files)
    if err != nil {
        fmt.Println("๋‹ค์ค‘ ์—…๋กœ๋“œ ์‹คํŒจ:", err)
    }
}

๐Ÿ“ฆ ํŒŒ์ผ ์—…๋กœ๋“œ ์ฃผ์˜์‚ฌํ•ญ

1. ํŒŒ์ผ ํฌ๊ธฐ ์ œํ•œ: ์„œ๋ฒ„์˜ ์ตœ๋Œ€ ์—…๋กœ๋“œ ํฌ๊ธฐ๋ฅผ ํ™•์ธํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ์—์„œ๋„ ๊ฒ€์ฆํ•ด์•ผ ํ•ด
2. ํƒ€์ž„์•„์›ƒ ์„ค์ •: ํฐ ํŒŒ์ผ์€ ์—…๋กœ๋“œ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋‹ˆ ํƒ€์ž„์•„์›ƒ์„ ์ถฉ๋ถ„ํžˆ ๊ธธ๊ฒŒ ์„ค์ •
3. ์ง„ํ–‰๋ฅ  ํ‘œ์‹œ: io.TeeReader๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์—…๋กœ๋“œ ์ง„ํ–‰๋ฅ ์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์–ด
4. ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ: ๋งค์šฐ ํฐ ํŒŒ์ผ์€ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์–ด

๐Ÿ” HTTPS์™€ TLS ์„ค์ •

๋ณด์•ˆ์€ ์„ ํƒ์ด ์•„๋‹ˆ๋ผ ํ•„์ˆ˜์•ผ! ํŠนํžˆ ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃฐ ๋•Œ๋Š” HTTPS๋ฅผ ๋ฐ˜๋“œ์‹œ ์‚ฌ์šฉํ•ด์•ผ ํ•ด. Go๋Š” TLS ์„ค์ •๋„ ์•„์ฃผ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด.

package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "io"
    "net/http"
    "os"
)

// ๊ธฐ๋ณธ HTTPS ํด๋ผ์ด์–ธํŠธ
func basicHTTPSClient() {
    // ๊ธฐ๋ณธ ์„ค์ •์œผ๋กœ๋„ HTTPS๋Š” ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋ผ
    resp, err := http.Get("https://api.github.com")
    if err != nil {
        fmt.Println("์š”์ฒญ ์‹คํŒจ:", err)
        return
    }
    defer resp.Body.Close()
    
    fmt.Println("HTTPS ์š”์ฒญ ์„ฑ๊ณต!")
}

// ์ปค์Šคํ…€ TLS ์„ค์ •
func customTLSClient() {
    tlsConfig := &tls.Config{
        // ์ตœ์†Œ TLS ๋ฒ„์ „ ์„ค์ •
        MinVersion: tls.VersionTLS12,
        
        // ์„ ํ˜ธํ•˜๋Š” ์•”ํ˜ธํ™” ์Šค์œ„ํŠธ
        CipherSuites: []uint16{
            tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
        },
        
        // ์ธ์ฆ์„œ ๊ฒ€์ฆ (ํ”„๋กœ๋•์…˜์—์„œ๋Š” true์—ฌ์•ผ ํ•จ!)
        InsecureSkipVerify: false,
    }

    transport := &http.Transport{
        TLSClientConfig: tlsConfig,
    }

    client := &http.Client{
        Transport: transport,
    }

    resp, err := client.Get("https://api.github.com")
    if err != nil {
        fmt.Println("์š”์ฒญ ์‹คํŒจ:", err)
        return
    }
    defer resp.Body.Close()
    
    fmt.Println("์ปค์Šคํ…€ TLS ์„ค์ •์œผ๋กœ ์š”์ฒญ ์„ฑ๊ณต!")
}

// ์ž์ฒด ์„œ๋ช… ์ธ์ฆ์„œ ์‚ฌ์šฉ (๊ฐœ๋ฐœ ํ™˜๊ฒฝ)
func selfSignedCertClient(certFile string) error {
    // ์ธ์ฆ์„œ ํŒŒ์ผ ์ฝ๊ธฐ
    caCert, err := os.ReadFile(certFile)
    if err != nil {
        return fmt.Errorf("์ธ์ฆ์„œ ์ฝ๊ธฐ ์‹คํŒจ: %w", err)
    }

    // ์ธ์ฆ์„œ ํ’€ ์ƒ์„ฑ
    caCertPool := x509.NewCertPool()
    if !caCertPool.AppendCertsFromPEM(caCert) {
        return fmt.Errorf("์ธ์ฆ์„œ ํŒŒ์‹ฑ ์‹คํŒจ")
    }

    // TLS ์„ค์ •
    tlsConfig := &tls.Config{
        RootCAs: caCertPool,
    }

    transport := &http.Transport{
        TLSClientConfig: tlsConfig,
    }

    client := &http.Client{
        Transport: transport,
    }

    resp, err := client.Get("https://localhost:8443")
    if err != nil {
        return fmt.Errorf("์š”์ฒญ ์‹คํŒจ: %w", err)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println("์‘๋‹ต:", string(body))
    return nil
}

// ํด๋ผ์ด์–ธํŠธ ์ธ์ฆ์„œ ์‚ฌ์šฉ (mTLS)
func mutualTLSClient(certFile, keyFile, caFile string) error {
    // ํด๋ผ์ด์–ธํŠธ ์ธ์ฆ์„œ ๋กœ๋“œ
    cert, err := tls.LoadX509KeyPair(certFile, keyFile)
    if err != nil {
        return fmt.Errorf("ํด๋ผ์ด์–ธํŠธ ์ธ์ฆ์„œ ๋กœ๋“œ ์‹คํŒจ: %w", err)
    }

    // CA ์ธ์ฆ์„œ ๋กœ๋“œ
    caCert, err := os.ReadFile(caFile)
    if err != nil {
        return fmt.Errorf("CA ์ธ์ฆ์„œ ์ฝ๊ธฐ ์‹คํŒจ: %w", err)
    }

    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)

    // TLS ์„ค์ •
    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        RootCAs:      caCertPool,
    }

    transport := &http.Transport{
        TLSClientConfig: tlsConfig,
    }

    client := &http.Client{
        Transport: transport,
    }

    resp, err := client.Get("https://secure-api.example.com")
    if err != nil {
        return fmt.Errorf("์š”์ฒญ ์‹คํŒจ: %w", err)
    }
    defer resp.Body.Close()

    fmt.Println("mTLS ์ธ์ฆ ์„ฑ๊ณต!")
    return nil
}

func main() {
    // ๊ธฐ๋ณธ HTTPS
    basicHTTPSClient()

    // ์ปค์Šคํ…€ TLS
    customTLSClient()

    // ์ž์ฒด ์„œ๋ช… ์ธ์ฆ์„œ (์˜ˆ์ œ)
    // selfSignedCertClient("./ca.crt")

    // mTLS (์˜ˆ์ œ)
    // mutualTLSClient("./client.crt", "./client.key", "./ca.crt")
}

โš ๏ธ ๋ณด์•ˆ ๊ฒฝ๊ณ 

InsecureSkipVerify: true๋Š” ์ ˆ๋Œ€๋กœ ํ”„๋กœ๋•์…˜์—์„œ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ ๋ผ! ์ด๊ฑด ๊ฐœ๋ฐœ/ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ๋งŒ ์ž„์‹œ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฑฐ์•ผ. ์ธ์ฆ์„œ ๊ฒ€์ฆ์„ ๊ฑด๋„ˆ๋›ฐ๋ฉด ์ค‘๊ฐ„์ž ๊ณต๊ฒฉ(MITM)์— ์ทจ์•ฝํ•ด์ ธ์„œ ์ •๋ง ์œ„ํ—˜ํ•ด. ๐Ÿ˜ฑ

โšก ๋™์‹œ์„ฑ๊ณผ ๊ณ ๋ฃจํ‹ด์„ ํ™œ์šฉํ•œ ๋ณ‘๋ ฌ ์š”์ฒญ

Go์˜ ์ง„์งœ ๊ฐ•์ ์€ ๋ฐ”๋กœ ๋™์‹œ์„ฑ์ด์•ผ! ์—ฌ๋Ÿฌ API๋ฅผ ๋™์‹œ์— ํ˜ธ์ถœํ•ด์•ผ ํ•  ๋•Œ ๊ณ ๋ฃจํ‹ด์„ ์‚ฌ์šฉํ•˜๋ฉด ์—„์ฒญ๋‚œ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์–ป์„ ์ˆ˜ ์žˆ์–ด. ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์—ฌ๋Ÿฌ ์™ธ๋ถ€ ์„œ๋น„์Šค๋ฅผ ๋™์‹œ์— ์กฐํšŒํ•  ๋•Œ ํŠนํžˆ ์œ ์šฉํ•˜์ง€! ๐Ÿš€

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "sync"
    "time"
)

// ์š”์ฒญ ๊ฒฐ๊ณผ ๊ตฌ์กฐ์ฒด
type Result struct {
    URL      string
    Status   int
    Body     string
    Duration time.Duration
    Error    error
}

// ๋‹จ์ผ URL ์š”์ฒญ ํ•จ์ˆ˜
func fetchURL(ctx context.Context, url string) Result {
    start := time.Now()
    result := Result{URL: url}

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        result.Error = err
        return result
    }

    client := &http.Client{Timeout: 10 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
        result.Error = err
        result.Duration = time.Since(start)
        return result
    }
    defer resp.Body.Close()

    result.Status = resp.StatusCode
    body, _ := io.ReadAll(resp.Body)
    result.Body = string(body)
    result.Duration = time.Since(start)

    return result
}

// ์—ฌ๋Ÿฌ URL ๋ณ‘๋ ฌ ์š”์ฒญ (์ฑ„๋„ ์‚ฌ์šฉ)
func fetchMultipleURLs(urls []string) []Result {
    results := make([]Result, len(urls))
    resultChan := make(chan Result, len(urls))

    ctx := context.Background()

    // ๊ฐ URL์— ๋Œ€ํ•ด ๊ณ ๋ฃจํ‹ด ์‹œ์ž‘
    for _, url := range urls {
        go func(u string) {
            resultChan <- fetchURL(ctx, u)
        }(url)
    }

    // ๊ฒฐ๊ณผ ์ˆ˜์ง‘
    for i := 0; i < len(urls); i++ {
        results[i] = <-resultChan
    }

    return results
}

// WaitGroup์„ ์‚ฌ์šฉํ•œ ๋ณ‘๋ ฌ ์š”์ฒญ
func fetchWithWaitGroup(urls []string) []Result {
    var wg sync.WaitGroup
    results := make([]Result, len(urls))
    ctx := context.Background()

    for i, url := range urls {
        wg.Add(1)
        go func(index int, u string) {
            defer wg.Done()
            results[index] = fetchURL(ctx, u)
        }(i, url)
    }

    wg.Wait()
    return results
}

// ๋™์‹œ ์š”์ฒญ ์ˆ˜ ์ œํ•œ (์›Œ์ปค ํ’€ ํŒจํ„ด)
func fetchWithWorkerPool(urls []string, maxWorkers int) []Result {
    jobs := make(chan string, len(urls))
    results := make(chan Result, len(urls))

    ctx := context.Background()

    // ์›Œ์ปค ์‹œ์ž‘
    var wg sync.WaitGroup
    for i := 0; i < maxWorkers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for url := range jobs {
                results <- fetchURL(ctx, url)
            }
        }()
    }

    // ์ž‘์—… ์ „์†ก
    for _, url := range urls {
        jobs <- url
    }
    close(jobs)

    // ์›Œ์ปค ์ข…๋ฃŒ ๋Œ€๊ธฐ
    go func() {
        wg.Wait()
        close(results)
    }()

    // ๊ฒฐ๊ณผ ์ˆ˜์ง‘
    var finalResults []Result
    for result := range results {
        finalResults = append(finalResults, result)
    }

    return finalResults
}

// ํƒ€์ž„์•„์›ƒ์ด ์žˆ๋Š” ๋ณ‘๋ ฌ ์š”์ฒญ
func fetchWithTimeout(urls []string, timeout time.Duration) []Result {
    ctx, cancel := context.WithTimeout(context.Background(), timeout)
    defer cancel()

    results := make([]Result, len(urls))
    var wg sync.WaitGroup

    for i, url := range urls {
        wg.Add(1)
        go func(index int, u string) {
            defer wg.Done()
            results[index] = fetchURL(ctx, u)
        }(i, url)
    }

    wg.Wait()
    return results
}

func main() {
    urls := []string{
        "https://api.github.com/users/golang",
        "https://api.github.com/users/kubernetes",
        "https://api.github.com/users/docker",
        "https://jsonplaceholder.typicode.com/posts/1",
        "https://jsonplaceholder.typicode.com/posts/2",
    }

    fmt.Println("=== ์ˆœ์ฐจ ์š”์ฒญ ===")
    start := time.Now()
    for _, url := range urls {
        result := fetchURL(context.Background(), url)
        fmt.Printf("%s: %d (%v)\n", result.URL, result.Status, result.Duration)
    }
    fmt.Printf("์ด ์†Œ์š” ์‹œ๊ฐ„: %v\n\n", time.Since(start))

    fmt.Println("=== ๋ณ‘๋ ฌ ์š”์ฒญ (์ฑ„๋„) ===")
    start = time.Now()
    results := fetchMultipleURLs(urls)
    for _, result := range results {
        if result.Error != nil {
            fmt.Printf("%s: ์—๋Ÿฌ - %v\n", result.URL, result.Error)
        } else {
            fmt.Printf("%s: %d (%v)\n", result.URL, result.Status, result.Duration)
        }
    }
    fmt.Printf("์ด ์†Œ์š” ์‹œ๊ฐ„: %v\n\n", time.Since(start))

    fmt.Println("=== ์›Œ์ปค ํ’€ (๋™์‹œ ์š”์ฒญ 2๊ฐœ ์ œํ•œ) ===")
    start = time.Now()
    results = fetchWithWorkerPool(urls, 2)
    for _, result := range results {
        if result.Error != nil {
            fmt.Printf("%s: ์—๋Ÿฌ - %v\n", result.URL, result.Error)
        } else {
            fmt.Printf("%s: %d (%v)\n", result.URL, result.Status, result.Duration)
        }
    }
    fmt.Printf("์ด ์†Œ์š” ์‹œ๊ฐ„: %v\n\n", time.Since(start))

    fmt.Println("=== ํƒ€์ž„์•„์›ƒ 5์ดˆ ===")
    start = time.Now()
    results = fetchWithTimeout(urls, 5*time.Second)
    for _, result := range results {
        if result.Error != nil {
            fmt.Printf("%s: ์—๋Ÿฌ - %v\n", result.URL, result.Error)
        } else {
            fmt.Printf("%s: %d (%v)\n", result.URL, result.Status, result.Duration)
        }
    }
    fmt.Printf("์ด ์†Œ์š” ์‹œ๊ฐ„: %v\n", time.Since(start))
}

๐ŸŽฏ ๋ณ‘๋ ฌ ์š”์ฒญ ํŒจํ„ด ๋น„๊ต

์ฑ„๋„ ํŒจํ„ด: ๊ฐ€์žฅ Go๋‹ค์šด ๋ฐฉ์‹. ๊ฒฐ๊ณผ๋ฅผ ์ฑ„๋„๋กœ ์ „๋‹ฌ๋ฐ›์•„ ์ฒ˜๋ฆฌ
WaitGroup ํŒจํ„ด: ๋ชจ๋“  ๊ณ ๋ฃจํ‹ด์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ. ๊ฐ„๋‹จํ•˜๊ณ  ์ง๊ด€์ 
์›Œ์ปค ํ’€ ํŒจํ„ด: ๋™์‹œ ์š”์ฒญ ์ˆ˜๋ฅผ ์ œํ•œ. ์„œ๋ฒ„ ๋ถ€ํ•˜ ์กฐ์ ˆ์— ์œ ์šฉ
์ปจํ…์ŠคํŠธ ํŒจํ„ด: ํƒ€์ž„์•„์›ƒ๊ณผ ์ทจ์†Œ๋ฅผ ์šฐ์•„ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ

์ˆœ์ฐจ ์š”์ฒญ๊ณผ ๋ณ‘๋ ฌ ์š”์ฒญ์˜ ์„ฑ๋Šฅ ์ฐจ์ด๋Š” ์ •๋ง ์—„์ฒญ๋‚˜. 5๊ฐœ URL์„ ์ˆœ์ฐจ๋กœ ์š”์ฒญํ•˜๋ฉด ๊ฐ๊ฐ 1์ดˆ์”ฉ ์ด 5์ดˆ๊ฐ€ ๊ฑธ๋ฆฌ์ง€๋งŒ, ๋ณ‘๋ ฌ๋กœ ์š”์ฒญํ•˜๋ฉด 1์ดˆ ์ •๋„๋ฉด ๋๋‚˜! ๐Ÿ˜Ž

๐ŸŽจ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ๋‚ ์”จ API ํด๋ผ์ด์–ธํŠธ

์ด์ œ ๋ฐฐ์šด ๊ฑธ ๋ชจ๋‘ ํ™œ์šฉํ•ด์„œ ์‹ค์šฉ์ ์ธ ๋‚ ์”จ API ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž! ์ด๋Ÿฐ ํŒจํ„ด์€ ์–ด๋–ค ์™ธ๋ถ€ API ์—ฐ๋™์—๋„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
    "time"
)

// ๋‚ ์”จ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์ฒด
type WeatherResponse struct {
    Location struct {
        Name    string `json:"name"`
        Country string `json:"country"`
    } `json:"location"`
    Current struct {
        TempC     float64 `json:"temp_c"`
        Condition struct {
            Text string `json:"text"`
        } `json:"condition"`
        Humidity int     `json:"humidity"`
        WindKph  float64 `json:"wind_kph"`
    } `json:"current"`
}

// ๋‚ ์”จ API ํด๋ผ์ด์–ธํŠธ
type WeatherClient struct {
    BaseURL    string
    APIKey     string
    HTTPClient *http.Client
}

// ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
func NewWeatherClient(apiKey string) *WeatherClient {
    return &WeatherClient{
        BaseURL: "https://api.weatherapi.com/v1",
        APIKey:  apiKey,
        HTTPClient: &http.Client{
            Timeout: 15 * time.Second,
            Transport: &http.Transport{
                MaxIdleConnsPerHost: 5,
                IdleConnTimeout:     60 * time.Second,
            },
        },
    }
}

// ํ˜„์žฌ ๋‚ ์”จ ์กฐํšŒ
func (c *WeatherClient) GetCurrentWeather(ctx context.Context, city string) (*WeatherResponse, error) {
    // URL ํŒŒ๋ผ๋ฏธํ„ฐ ๊ตฌ์„ฑ
    params := url.Values{}
    params.Add("key", c.APIKey)
    params.Add("q", city)
    params.Add("aqi", "no")

    fullURL := fmt.Sprintf("%s/current.json?%s", c.BaseURL, params.Encode())

    // ์š”์ฒญ ์ƒ์„ฑ
    req, err := http.NewRequestWithContext(ctx, "GET", fullURL, nil)
    if err != nil {
        return nil, fmt.Errorf("์š”์ฒญ ์ƒ์„ฑ ์‹คํŒจ: %w", err)
    }

    // ํ—ค๋” ์„ค์ •
    req.Header.Set("Accept", "application/json")

    // ์š”์ฒญ ์‹คํ–‰
    resp, err := c.HTTPClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("์š”์ฒญ ์‹คํŒจ: %w", err)
    }
    defer resp.Body.Close()

    // ์ƒํƒœ ์ฝ”๋“œ ํ™•์ธ
    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("API ์—๋Ÿฌ: ์ƒํƒœ ์ฝ”๋“œ %d", resp.StatusCode)
    }

    // JSON ๋””์ฝ”๋”ฉ
    var weather WeatherResponse
    if err := json.NewDecoder(resp.Body).Decode(&weather); err != nil {
        return nil, fmt.Errorf("JSON ๋””์ฝ”๋”ฉ ์‹คํŒจ: %w", err)
    }

    return &weather, nil
}

// ์—ฌ๋Ÿฌ ๋„์‹œ์˜ ๋‚ ์”จ ๋™์‹œ ์กฐํšŒ
func (c *WeatherClient) GetMultipleCitiesWeather(ctx context.Context, cities []string) map[string]*WeatherResponse {
    results := make(map[string]*WeatherResponse)
    resultChan := make(chan struct {
        city    string
        weather *WeatherResponse
        err     error
    }, len(cities))

    // ๊ฐ ๋„์‹œ์— ๋Œ€ํ•ด ๊ณ ๋ฃจํ‹ด ์‹œ์ž‘
    for _, city := range cities {
        go func(c string) {
            weather, err := c.GetCurrentWeather(ctx, c)
            resultChan <- struct {
                city    string
                weather *WeatherResponse
                err     error
            }{c, weather, err}
        }(city)
    }

    // ๊ฒฐ๊ณผ ์ˆ˜์ง‘
    for i := 0; i < len(cities); i++ {
        result := <-resultChan
        if result.err == nil {
            results[result.city] = result.weather
        } else {
            fmt.Printf("๋„์‹œ %s ์กฐํšŒ ์‹คํŒจ: %v\n", result.city, result.err)
        }
    }

    return results
}

// ๋‚ ์”จ ์ •๋ณด ์ถœ๋ ฅ
func printWeather(city string, weather *WeatherResponse) {
    fmt.Printf("\n๐ŸŒ %s, %s\n", weather.Location.Name, weather.Location.Country)
    fmt.Printf("๐ŸŒก๏ธ  ์˜จ๋„: %.1fยฐC\n", weather.Current.TempC)
    fmt.Printf("โ˜๏ธ  ๋‚ ์”จ: %s\n", weather.Current.Condition.Text)
    fmt.Printf("๐Ÿ’ง ์Šต๋„: %d%%\n", weather.Current.Humidity)
    fmt.Printf("๐Ÿ’จ ํ’์†: %.1f km/h\n", weather.Current.WindKph)
}

func main() {
    // API ํ‚ค๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒŒ ์ข‹์•„
    apiKey := "YOUR_API_KEY_HERE"
    
    client := NewWeatherClient(apiKey)
    ctx := context.Background()

    // ๋‹จ์ผ ๋„์‹œ ์กฐํšŒ
    fmt.Println("=== ์„œ์šธ ๋‚ ์”จ ์กฐํšŒ ===")
    weather, err := client.GetCurrentWeather(ctx, "Seoul")
    if err != nil {
        fmt.Println("๋‚ ์”จ ์กฐํšŒ ์‹คํŒจ:", err)
        return
    }
    printWeather("Seoul", weather)

    // ์—ฌ๋Ÿฌ ๋„์‹œ ๋™์‹œ ์กฐํšŒ
    fmt.Println("\n=== ์—ฌ๋Ÿฌ ๋„์‹œ ๋‚ ์”จ ๋™์‹œ ์กฐํšŒ ===")
    cities := []string{"Seoul", "Tokyo", "New York", "London", "Paris"}
    
    start := time.Now()
    results := client.GetMultipleCitiesWeather(ctx, cities)
    duration := time.Since(start)

    for city, weather := range results {
        printWeather(city, weather)
    }

    fmt.Printf("\nโฑ๏ธ  ์ด ์†Œ์š” ์‹œ๊ฐ„: %v (๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ)\n", duration)
}

๐Ÿ’ก ์‹ค์ „ ํŒ: API ํด๋ผ์ด์–ธํŠธ ์„ค๊ณ„ ์›์น™

1. ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ

ํด๋ผ์ด์–ธํŠธ๋ฅผ ๊ตฌ์กฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์„œ ์„ค์ •์„ ์บก์Аํ™”ํ•˜๊ณ , ๋ฉ”์„œ๋“œ๋กœ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด.

2. ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ผ๊ด€์„ฑ

๋ชจ๋“  ์—๋Ÿฌ์— ์ปจํ…์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ๋””๋ฒ„๊น…์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด. fmt.Errorf์™€ %w๋ฅผ ํ™œ์šฉํ•˜์ž!

3. ํƒ€์ž„์•„์›ƒ ํ•„์ˆ˜

๋ชจ๋“  ์™ธ๋ถ€ ์š”์ฒญ์—๋Š” ๋ฐ˜๋“œ์‹œ ํƒ€์ž„์•„์›ƒ์„ ์„ค์ •ํ•ด์•ผ ํ•ด. ๋ฌดํ•œ ๋Œ€๊ธฐ๋Š” ์ ˆ๋Œ€ ๊ธˆ๋ฌผ!

4. ์—ฐ๊ฒฐ ์žฌ์‚ฌ์šฉ

HTTP ํด๋ผ์ด์–ธํŠธ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋ฉด ์—ฐ๊ฒฐ ํ’€์˜ ์ด์ ์„ ๋ˆ„๋ฆด ์ˆ˜ ์žˆ์–ด. ๋งค๋ฒˆ ์ƒˆ๋กœ ๋งŒ๋“ค์ง€ ๋ง์ž!

5. ์ปจํ…์ŠคํŠธ ์ „ํŒŒ

๋ชจ๋“  ํ•จ์ˆ˜์— context๋ฅผ ์ „๋‹ฌํ•ด์„œ ์ทจ์†Œ์™€ ํƒ€์ž„์•„์›ƒ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด.

๐Ÿ›ก๏ธ ๋ ˆ์ดํŠธ ๋ฆฌ๋ฏธํŒ…๊ณผ ๋ฐฑ์˜คํ”„ ์ „๋žต

์™ธ๋ถ€ API๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๊ฐ€์žฅ ์กฐ์‹ฌํ•ด์•ผ ํ•  ๊ฒŒ ๋ฐ”๋กœ ์š”์ฒญ ์ œํ•œ์ด์•ผ. ๋„ˆ๋ฌด ๋งŽ์€ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ์ฐจ๋‹จ๋‹นํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๋ ˆ์ดํŠธ ๋ฆฌ๋ฏธํŒ…๊ณผ ๋ฐฑ์˜คํ”„ ์ „๋žต์„ ๊ตฌํ˜„ํ•ด๋ณด์ž!

package main

import (
    "context"
    "fmt"
    "io"
    "math"
    "net/http"
    "time"
)

// ๋ ˆ์ดํŠธ ๋ฆฌ๋ฏธํ„ฐ ๊ตฌ์กฐ์ฒด
type RateLimiter struct {
    ticker   *time.Ticker
    requests chan struct{}
}

// ๋ ˆ์ดํŠธ ๋ฆฌ๋ฏธํ„ฐ ์ƒ์„ฑ (์ดˆ๋‹น ์š”์ฒญ ์ˆ˜ ์ œํ•œ)
func NewRateLimiter(requestsPerSecond int) *RateLimiter {
    rl := &RateLimiter{
        ticker:   time.NewTicker(time.Second / time.Duration(requestsPerSecond)),
        requests: make(chan struct{}, requestsPerSecond),
    }

    // ํ‹ฐ์ปค๋กœ ํ† ํฐ ์ƒ์„ฑ
    go func() {
        for range rl.ticker.C {
            select {
            case rl.requests <- struct{}{}:
            default:
            }
        }
    }()

    return rl
}

// ์š”์ฒญ ํ—ˆ๊ฐ€ ๋Œ€๊ธฐ
func (rl *RateLimiter) Wait(ctx context.Context) error {
    select {
    case <-rl.requests:
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
}

// ์ •๋ฆฌ
func (rl *RateLimiter) Stop() {
    rl.ticker.Stop()
    close(rl.requests)
}

// ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„ ์žฌ์‹œ๋„
func retryWithExponentialBackoff(
    ctx context.Context,
    maxRetries int,
    initialDelay time.Duration,
    maxDelay time.Duration,
    fn func() error,
) error {
    var err error
    delay := initialDelay

    for i := 0; i < maxRetries; i++ {
        // ํ•จ์ˆ˜ ์‹คํ–‰
        err = fn()
        if err == nil {
            return nil
        }

        // ๋งˆ์ง€๋ง‰ ์‹œ๋„๋ฉด ์—๋Ÿฌ ๋ฐ˜ํ™˜
        if i == maxRetries-1 {
            break
        }

        // ์ปจํ…์ŠคํŠธ ์ทจ์†Œ ํ™•์ธ
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
        }

        // ๋ฐฑ์˜คํ”„ ๋Œ€๊ธฐ
        fmt.Printf("์žฌ์‹œ๋„ %d/%d: %v ํ›„ ์žฌ์‹œ๋„...\n", i+1, maxRetries, delay)
        select {
        case <-time.After(delay):
        case <-ctx.Done():
            return ctx.Err()
        }

        // ์ง€์ˆ˜์ ์œผ๋กœ ์ง€์—ฐ ์‹œ๊ฐ„ ์ฆ๊ฐ€ (์ตœ๋Œ€๊ฐ’ ์ œํ•œ)
        delay = time.Duration(math.Min(
            float64(delay*2),
            float64(maxDelay),
        ))
    }

    return fmt.Errorf("์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜ ์ดˆ๊ณผ: %w", err)
}

// ๋ ˆ์ดํŠธ ๋ฆฌ๋ฏธํŒ…์ด ์ ์šฉ๋œ HTTP ํด๋ผ์ด์–ธํŠธ
type RateLimitedClient struct {
    client      *http.Client
    rateLimiter *RateLimiter
}

func NewRateLimitedClient(requestsPerSecond int) *RateLimitedClient {
    return &RateLimitedClient{
        client: &http.Client{
            Timeout: 30 * time.Second,
        },
        rateLimiter: NewRateLimiter(requestsPerSecond),
    }
}

func (c *RateLimitedClient) Get(ctx context.Context, url string) (*http.Response, error) {
    // ๋ ˆ์ดํŠธ ๋ฆฌ๋ฏธํ„ฐ ๋Œ€๊ธฐ
    if err := c.rateLimiter.Wait(ctx); err != nil {
        return nil, err
    }

    // ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„๋กœ ์žฌ์‹œ๋„
    var resp *http.Response
    err := retryWithExponentialBackoff(
        ctx,
        3,                    // ์ตœ๋Œ€ 3๋ฒˆ ์žฌ์‹œ๋„
        1*time.Second,        // ์ดˆ๊ธฐ ์ง€์—ฐ 1์ดˆ
        10*time.Second,       // ์ตœ๋Œ€ ์ง€์—ฐ 10์ดˆ
        func() error {
            req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
            if err != nil {
                return err
            }

            resp, err = c.client.Do(req)
            if err != nil {
                return err
            }

            // 429 (Too Many Requests) ์ƒํƒœ๋ฉด ์žฌ์‹œ๋„
            if resp.StatusCode == http.StatusTooManyRequests {
                resp.Body.Close()
                return fmt.Errorf("์š”์ฒญ ์ œํ•œ ์ดˆ๊ณผ")
            }

            // 5xx ์„œ๋ฒ„ ์—๋Ÿฌ๋„ ์žฌ์‹œ๋„
            if resp.StatusCode >= 500 {
                resp.Body.Close()
                return fmt.Errorf("์„œ๋ฒ„ ์—๋Ÿฌ: %d", resp.StatusCode)
            }

            return nil
        },
    )

    return resp, err
}

func (c *RateLimitedClient) Close() {
    c.rateLimiter.Stop()
}

func main() {
    // ์ดˆ๋‹น 2๊ฐœ ์š”์ฒญ์œผ๋กœ ์ œํ•œ
    client := NewRateLimitedClient(2)
    defer client.Close()

    ctx := context.Background()
    urls := []string{
        "https://api.github.com/users/golang",
        "https://api.github.com/users/kubernetes",
        "https://api.github.com/users/docker",
        "https://api.github.com/users/prometheus",
        "https://api.github.com/users/grafana",
    }

    fmt.Println("๋ ˆ์ดํŠธ ๋ฆฌ๋ฏธํŒ… ์ ์šฉ ์š”์ฒญ ์‹œ์ž‘...")
    start := time.Now()

    for i, url := range urls {
        fmt.Printf("\n[%d] ์š”์ฒญ: %s\n", i+1, url)
        
        resp, err := client.Get(ctx, url)
        if err != nil {
            fmt.Printf("โŒ ์‹คํŒจ: %v\n", err)
            continue
        }

        body, _ := io.ReadAll(resp.Body)
        resp.Body.Close()

        fmt.Printf("โœ… ์„ฑ๊ณต: ์ƒํƒœ %d, ํฌ๊ธฐ %d bytes\n", resp.StatusCode, len(body))
    }

    fmt.Printf("\n์ด ์†Œ์š” ์‹œ๊ฐ„: %v\n", time.Since(start))
    fmt.Println("(๋ ˆ์ดํŠธ ๋ฆฌ๋ฏธํŒ…์œผ๋กœ ์ธํ•ด ์š”์ฒญ์ด ๋ถ„์‚ฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค)")
}

๐ŸŽฏ ๋ฐฑ์˜คํ”„ ์ „๋žต ์„ ํƒ ๊ฐ€์ด๋“œ

๊ณ ์ • ๋ฐฑ์˜คํ”„: ๋งค๋ฒˆ ๊ฐ™์€ ์‹œ๊ฐ„ ๋Œ€๊ธฐ. ๊ฐ„๋‹จํ•˜์ง€๋งŒ ํšจ์œจ์ ์ด์ง€ ์•Š์Œ
์„ ํ˜• ๋ฐฑ์˜คํ”„: ์‹œ๋„๋งˆ๋‹ค ์ผ์ • ์‹œ๊ฐ„์”ฉ ์ฆ๊ฐ€. ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ๋А๋ฆด ์ˆ˜ ์žˆ์Œ
์ง€์ˆ˜ ๋ฐฑ์˜คํ”„: ์‹œ๋„๋งˆ๋‹ค 2๋ฐฐ์”ฉ ์ฆ๊ฐ€. ๊ฐ€์žฅ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ์‹
์ง€ํ„ฐ ์ถ”๊ฐ€: ๋žœ๋ค ์š”์†Œ๋ฅผ ๋”ํ•ด์„œ ๋™์‹œ ์žฌ์‹œ๋„๋ฅผ ๋ถ„์‚ฐ์‹œํ‚ด

๐ŸŽช ์›นํ›…๊ณผ ์ฝœ๋ฐฑ ์ฒ˜๋ฆฌ

์›นํ›…์€ ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ด๋ฒคํŠธ๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ๋ฐฉ์‹์ด์•ผ. ๊ฒฐ์ œ ์™„๋ฃŒ, ์ฃผ๋ฌธ ์ƒํƒœ ๋ณ€๊ฒฝ ๋“ฑ์˜ ์•Œ๋ฆผ์„ ๋ฐ›์„ ๋•Œ ์‚ฌ์šฉํ•ด. ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ๊ฒฐ์ œ ์‹œ์Šคํ…œ๊ณผ ์—ฐ๋™ํ•  ๋•Œ ์›นํ›…์„ ๋งŽ์ด ์‚ฌ์šฉํ•˜์ง€!

package main

import (
    "bytes"
    "context"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

// ์›นํ›… ํŽ˜์ด๋กœ๋“œ
type WebhookPayload struct {
    Event     string                 `json:"event"`
    Timestamp int64                  `json:"timestamp"`
    Data      map[string]interface{} `json:"data"`
}

// ์›นํ›… ํด๋ผ์ด์–ธํŠธ
type WebhookClient struct {
    client    *http.Client
    secretKey string
}

func NewWebhookClient(secretKey string) *WebhookClient {
    return &WebhookClient{
        client: &http.Client{
            Timeout: 10 * time.Second,
        },
        secretKey: secretKey,
    }
}

// HMAC ์„œ๋ช… ์ƒ์„ฑ
func (c *WebhookClient) generateSignature(payload []byte) string {
    h := hmac.New(sha256.New, []byte(c.secretKey))
    h.Write(payload)
    return hex.EncodeToString(h.Sum(nil))
}

// ์›นํ›… ์ „์†ก
func (c *WebhookClient) SendWebhook(ctx context.Context, url string, payload WebhookPayload) error {
    // ํŽ˜์ด๋กœ๋“œ๋ฅผ JSON์œผ๋กœ ์ธ์ฝ”๋”ฉ
    jsonData, err := json.Marshal(payload)
    if err != nil {
        return fmt.Errorf("JSON ์ธ์ฝ”๋”ฉ ์‹คํŒจ: %w", err)
    }

    // ์„œ๋ช… ์ƒ์„ฑ
    signature := c.generateSignature(jsonData)

    // ์š”์ฒญ ์ƒ์„ฑ
    req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
    if err != nil {
        return fmt.Errorf("์š”์ฒญ ์ƒ์„ฑ ์‹คํŒจ: %w", err)
    }

    // ํ—ค๋” ์„ค์ •
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-Webhook-Signature", signature)
    req.Header.Set("X-Webhook-Timestamp", fmt.Sprintf("%d", payload.Timestamp))

    // ์žฌ์‹œ๋„ ๋กœ์ง ํฌํ•จ
    var resp *http.Response
    for i := 0; i < 3; i++ {
        resp, err = c.client.Do(req)
        if err == nil && resp.StatusCode == http.StatusOK {
            resp.Body.Close()
            return nil
        }

        if resp != nil {
            resp.Body.Close()
        }

        if i < 2 {
            time.Sleep(time.Duration(i+1) * time.Second)
        }
    }

    return fmt.Errorf("์›นํ›… ์ „์†ก ์‹คํŒจ: %w", err)
}

// ์›นํ›… ์ˆ˜์‹  ํ•ธ๋“ค๋Ÿฌ
type WebhookHandler struct {
    secretKey string
}

func NewWebhookHandler(secretKey string) *WebhookHandler {
    return &WebhookHandler{
        secretKey: secretKey,
    }
}

// ์„œ๋ช… ๊ฒ€์ฆ
func (h *WebhookHandler) verifySignature(payload []byte, signature string) bool {
    mac := hmac.New(sha256.New, []byte(h.secretKey))
    mac.Write(payload)
    expectedSignature := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(signature), []byte(expectedSignature))
}

// HTTP ํ•ธ๋“ค๋Ÿฌ
func (h *WebhookHandler) HandleWebhook(w http.ResponseWriter, r *http.Request) {
    // POST ๋ฉ”์„œ๋“œ๋งŒ ํ—ˆ์šฉ
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    // ๋ณธ๋ฌธ ์ฝ๊ธฐ
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Failed to read body", http.StatusBadRequest)
        return
    }
    defer r.Body.Close()

    // ์„œ๋ช… ๊ฒ€์ฆ
    signature := r.Header.Get("X-Webhook-Signature")
    if !h.verifySignature(body, signature) {
        http.Error(w, "Invalid signature", http.StatusUnauthorized)
        return
    }

    // ํƒ€์ž„์Šคํƒฌํ”„ ๊ฒ€์ฆ (5๋ถ„ ์ด๋‚ด)
    timestamp := r.Header.Get("X-Webhook-Timestamp")
    // ... ํƒ€์ž„์Šคํƒฌํ”„ ๊ฒ€์ฆ ๋กœ์ง ...

    // ํŽ˜์ด๋กœ๋“œ ํŒŒ์‹ฑ
    var payload WebhookPayload
    if err := json.Unmarshal(body, &payload); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }

    // ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
    if err := h.processEvent(payload); err != nil {
        http.Error(w, "Failed to process event", http.StatusInternalServerError)
        return
    }

    // ์„ฑ๊ณต ์‘๋‹ต
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
}

// ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
func (h *WebhookHandler) processEvent(payload WebhookPayload) error {
    fmt.Printf("์ด๋ฒคํŠธ ์ˆ˜์‹ : %s\n", payload.Event)
    fmt.Printf("๋ฐ์ดํ„ฐ: %+v\n", payload.Data)

    switch payload.Event {
    case "payment.completed":
        fmt.Println("๊ฒฐ์ œ ์™„๋ฃŒ ์ฒ˜๋ฆฌ")
        // ๊ฒฐ์ œ ์™„๋ฃŒ ๋กœ์ง...
    case "order.created":
        fmt.Println("์ฃผ๋ฌธ ์ƒ์„ฑ ์ฒ˜๋ฆฌ")
        // ์ฃผ๋ฌธ ์ƒ์„ฑ ๋กœ์ง...
    case "user.registered":
        fmt.Println("์‚ฌ์šฉ์ž ๋“ฑ๋ก ์ฒ˜๋ฆฌ")
        // ์‚ฌ์šฉ์ž ๋“ฑ๋ก ๋กœ์ง...
    default:
        fmt.Printf("์•Œ ์ˆ˜ ์—†๋Š” ์ด๋ฒคํŠธ: %s\n", payload.Event)
    }

    return nil
}

func main() {
    secretKey := "my-secret-key-12345"

    // ์›นํ›… ์ „์†ก ์˜ˆ์ œ
    client := NewWebhookClient(secretKey)
    
    payload := WebhookPayload{
        Event:     "payment.completed",
        Timestamp: time.Now().Unix(),
        Data: map[string]interface{}{
            "order_id": "ORD-12345",
            "amount":   50000,
            "currency": "KRW",
        },
    }

    ctx := context.Background()
    err := client.SendWebhook(ctx, "https://example.com/webhook", payload)
    if err != nil {
        fmt.Println("์›นํ›… ์ „์†ก ์‹คํŒจ:", err)
    } else {
        fmt.Println("์›นํ›… ์ „์†ก ์„ฑ๊ณต!")
    }

    // ์›นํ›… ์ˆ˜์‹  ์„œ๋ฒ„ ์˜ˆ์ œ
    handler := NewWebhookHandler(secretKey)
    http.HandleFunc("/webhook", handler.HandleWebhook)
    
    fmt.Println("์›นํ›… ์„œ๋ฒ„ ์‹œ์ž‘: http://localhost:8080/webhook")
    // http.ListenAndServe(":8080", nil)
}

๐Ÿ” ์›นํ›… ๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

โœ… ์„œ๋ช… ๊ฒ€์ฆ: HMAC์„ ์‚ฌ์šฉํ•ด์„œ ์š”์ฒญ์ด ์ •๋‹นํ•œ์ง€ ํ™•์ธ
โœ… ํƒ€์ž„์Šคํƒฌํ”„ ๊ฒ€์ฆ: ์žฌ์ „์†ก ๊ณต๊ฒฉ(Replay Attack) ๋ฐฉ์ง€
โœ… HTTPS ์‚ฌ์šฉ: ์ „์†ก ์ค‘ ๋ฐ์ดํ„ฐ ๋ณดํ˜ธ
โœ… IP ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ: ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” IP๋งŒ ํ—ˆ์šฉ
โœ… ์žฌ์‹œ๋„ ๋กœ์ง: ์ผ์‹œ์  ์‹คํŒจ์— ๋Œ€๋น„
โœ… ๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ: ๊ฐ™์€ ์ด๋ฒคํŠธ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ๋ฐ›์•„๋„ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ

๐Ÿ“Š ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๋กœ๊น…

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” HTTP ์š”์ฒญ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ๋กœ๊น…ํ•˜๋Š” ๊ฒŒ ์ •๋ง ์ค‘์š”ํ•ด. ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ ๋น ๋ฅด๊ฒŒ ์›์ธ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ !

package main

import (
    "context"
    "fmt"
    "io"
    "log"
    "net/http"
    "net/http/httputil"
    "time"
)

// ๋กœ๊น… ๋ฏธ๋“ค์›จ์–ด
type LoggingTransport struct {
    Transport http.RoundTripper
    Logger    *log.Logger
}

func (t *LoggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    start := time.Now()

    // ์š”์ฒญ ๋กœ๊น…
    t.Logger.Printf("โ†’ %s %s", req.Method, req.URL.String())

    // ์š”์ฒญ ์‹คํ–‰
    resp, err := t.Transport.RoundTrip(req)
    
    duration := time.Since(start)

    // ์‘๋‹ต ๋กœ๊น…
    if err != nil {
        t.Logger.Printf("โ† ์—๋Ÿฌ: %v (์†Œ์š”์‹œ๊ฐ„: %v)", err, duration)
        return nil, err
    }

    t.Logger.Printf("โ† %d %s (์†Œ์š”์‹œ๊ฐ„: %v)", 
        resp.StatusCode, 
        http.StatusText(resp.StatusCode),
        duration,
    )

    return resp, nil
}

// ์ƒ์„ธ ๋””๋ฒ„๊ทธ ๋กœ๊น…
type DebugTransport struct {
    Transport http.RoundTripper
}

func (t *DebugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    // ์š”์ฒญ ๋คํ”„
    reqDump, err := httputil.DumpRequestOut(req, true)
    if err != nil {
        log.Printf("์š”์ฒญ ๋คํ”„ ์‹คํŒจ: %v", err)
    } else {
        log.Printf("=== ์š”์ฒญ ===\n%s\n", string(reqDump))
    }

    // ์š”์ฒญ ์‹คํ–‰
    resp, err := t.Transport.RoundTrip(req)
    if err != nil {
        return nil, err
    }

    // ์‘๋‹ต ๋คํ”„
    respDump, err := httputil.DumpResponse(resp, true)
    if err != nil {
        log.Printf("์‘๋‹ต ๋คํ”„ ์‹คํŒจ: %v", err)
    } else {
        log.Printf("=== ์‘๋‹ต ===\n%s\n", string(respDump))
    }

    return resp, nil
}

// ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘
type MetricsCollector struct {
    TotalRequests   int64
    SuccessRequests int64
    FailedRequests  int64
    TotalDuration   time.Duration
}

type MetricsTransport struct {
    Transport http.RoundTripper
    Metrics   *MetricsCollector
}

func (t *MetricsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    start := time.Now()
    t.Metrics.TotalRequests++

    resp, err := t.Transport.RoundTrip(req)
    
    duration := time.Since(start)
    t.Metrics.TotalDuration += duration

    if err != nil {
        t.Metrics.FailedRequests++
        return nil, err
    }

    if resp.StatusCode >= 200 && resp.StatusCode < 300 {
        t.Metrics.SuccessRequests++
    } else {
        t.Metrics.FailedRequests++
    }

    return resp, nil
}

// ๋ฉ”ํŠธ๋ฆญ ์ถœ๋ ฅ
func (m *MetricsCollector) PrintStats() {
    fmt.Println("\n=== HTTP ํด๋ผ์ด์–ธํŠธ ํ†ต๊ณ„ ===")
    fmt.Printf("์ด ์š”์ฒญ ์ˆ˜: %d\n", m.TotalRequests)
    fmt.Printf("์„ฑ๊ณต: %d\n", m.SuccessRequests)
    fmt.Printf("์‹คํŒจ: %d\n", m.FailedRequests)
    if m.TotalRequests > 0 {
        avgDuration := m.TotalDuration / time.Duration(m.TotalRequests)
        fmt.Printf("ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„: %v\n", avgDuration)
    }
    if m.TotalRequests > 0 {
        successRate := float64(m.SuccessRequests) / float64(m.TotalRequests) * 100
        fmt.Printf("์„ฑ๊ณต๋ฅ : %.2f%%\n", successRate)
    }
}

// ์ข…ํ•ฉ ๋ชจ๋‹ˆํ„ฐ๋ง ํด๋ผ์ด์–ธํŠธ
func createMonitoredClient() (*http.Client, *MetricsCollector) {
    metrics := &MetricsCollector{}
    
    transport := &MetricsTransport{
        Transport: &LoggingTransport{
            Transport: http.DefaultTransport,
            Logger:    log.Default(),
        },
        Metrics: metrics,
    }

    client := &http.Client{
        Transport: transport,
        Timeout:   30 * time.Second,
    }

    return client, metrics
}

func main() {
    // ๋ชจ๋‹ˆํ„ฐ๋ง ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
    client, metrics := createMonitoredClient()

    urls := []string{
        "https://api.github.com/users/golang",
        "https://api.github.com/users/kubernetes",
        "https://jsonplaceholder.typicode.com/posts/1",
        "https://httpbin.org/status/404", // 404 ์—๋Ÿฌ
        "https://httpbin.org/delay/2",    // ์ง€์—ฐ
    }

    ctx := context.Background()

    // ์—ฌ๋Ÿฌ ์š”์ฒญ ์‹คํ–‰
    for _, url := range urls {
        req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
        resp, err := client.Do(req)
        
        if err != nil {
            log.Printf("์š”์ฒญ ์‹คํŒจ: %v", err)
            continue
        }

        io.Copy(io.Discard, resp.Body)
        resp.Body.Close()
    }

    // ํ†ต๊ณ„ ์ถœ๋ ฅ
    metrics.PrintStats()
}

๐Ÿ“ˆ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

๊ตฌ์กฐํ™”๋œ ๋กœ๊น…: JSON ํ˜•์‹์œผ๋กœ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋ฉด ๋ถ„์„์ด ์‰ฌ์›Œ์ ธ
๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘: ์‘๋‹ต ์‹œ๊ฐ„, ์—๋Ÿฌ์œจ, ์ฒ˜๋ฆฌ๋Ÿ‰ ๋“ฑ์„ ์ถ”์ ํ•ด
๋ถ„์‚ฐ ์ถ”์ : ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํ™˜๊ฒฝ์—์„œ๋Š” ์š”์ฒญ ID๋ฅผ ์ „ํŒŒํ•ด์„œ ์ถ”์ ํ•ด
์•Œ๋ฆผ ์„ค์ •: ์—๋Ÿฌ์œจ์ด๋‚˜ ์‘๋‹ต ์‹œ๊ฐ„์ด ์ž„๊ณ„๊ฐ’์„ ๋„˜์œผ๋ฉด ์•Œ๋ฆผ์„ ๋ฐ›๋„๋ก ์„ค์ •ํ•ด
๋Œ€์‹œ๋ณด๋“œ: Grafana ๊ฐ™์€ ๋„๊ตฌ๋กœ ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๋งŒ๋“ค์–ด

๐ŸŽ“ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์–ด. ๐Ÿ˜Š Go์˜ HTTP ํด๋ผ์ด์–ธํŠธ์™€ net/http ํŒจํ‚ค์ง€๋ฅผ ํ™œ์šฉํ•œ ์™ธ๋ถ€ ์„œ๋น„์Šค ์—ฐ๋™์— ๋Œ€ํ•ด ์ •๋ง ๋งŽ์€ ๊ฑธ ๋‹ค๋ค˜์ง€?

๊ธฐ๋ณธ์ ์ธ GET/POST ์š”์ฒญ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ, ์ปค์Šคํ…€ ํด๋ผ์ด์–ธํŠธ ์„ค์ •, ์ธ์ฆ, ํŒŒ์ผ ์—…๋กœ๋“œ, TLS ๋ณด์•ˆ, ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ, ๋ ˆ์ดํŠธ ๋ฆฌ๋ฏธํŒ…, ์›นํ›…, ๋ชจ๋‹ˆํ„ฐ๋ง๊นŒ์ง€... ์‹ค๋ฌด์—์„œ ํ•„์š”ํ•œ ๊ฑฐ์˜ ๋ชจ๋“  ๋‚ด์šฉ์„ ์ปค๋ฒ„ํ–ˆ์–ด!

๐ŸŽฏ ํ•ต์‹ฌ ์š”์•ฝ

1. ๊ธฐ๋ณธ๊ธฐ๊ฐ€ ์ค‘์š”ํ•ด

http.Get()๊ณผ http.Post()๋กœ ์‹œ์ž‘ํ•˜๋˜, ์‹ค๋ฌด์—์„œ๋Š” ์ปค์Šคํ…€ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•ด.

2. ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋Š” ํ•„์ˆ˜

๋„คํŠธ์›Œํฌ๋Š” ์–ธ์ œ๋“  ์‹คํŒจํ•  ์ˆ˜ ์žˆ์–ด. ์žฌ์‹œ๋„ ๋กœ์ง๊ณผ ํƒ€์ž„์•„์›ƒ์„ ๋ฐ˜๋“œ์‹œ ๊ตฌํ˜„ํ•ด.

3. ์„ฑ๋Šฅ ์ตœ์ ํ™”

์—ฐ๊ฒฐ ํ’€์„ ํ™œ์šฉํ•˜๊ณ , ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋กœ ์„ฑ๋Šฅ์„ ๊ทน๋Œ€ํ™”ํ•ด.

4. ๋ณด์•ˆ์„ ์žŠ์ง€ ๋งˆ

HTTPS ์‚ฌ์šฉ, ์ธ์ฆ์„œ ๊ฒ€์ฆ, API ํ‚ค ๊ด€๋ฆฌ๋ฅผ ์ฒ ์ €ํžˆ ํ•ด.

5. ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๋กœ๊น…

ํ”„๋กœ๋•์…˜์—์„œ๋Š” ๋ฐ˜๋“œ์‹œ ๋กœ๊น…๊ณผ ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘์„ ๊ตฌํ˜„ํ•ด.

์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์„ ๊ฐœ๋ฐœํ•  ๋•Œ๋„ ์ด๋Ÿฐ HTTP ํด๋ผ์ด์–ธํŠธ ๊ธฐ์ˆ ์ด ์ •๋ง ์ค‘์š”ํ•ด. ๊ฒฐ์ œ ์‹œ์Šคํ…œ, ์•Œ๋ฆผ ์„œ๋น„์Šค, ์™ธ๋ถ€ API ์—ฐ๋™ ๋“ฑ ๋ชจ๋“  ๊ณณ์—์„œ ์‚ฌ์šฉ๋˜๊ฑฐ๋“ . ์ด ๊ธ€์—์„œ ๋ฐฐ์šด ๋‚ด์šฉ๋“ค์„ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณด๋ฉด์„œ ๊ฒฝํ—˜์„ ์Œ“์•„๊ฐ€๊ธธ ๋ฐ”๋ผ! ๐Ÿ’ช

Go์˜ net/http ํŒจํ‚ค์ง€๋Š” ์ •๋ง ์ž˜ ์„ค๊ณ„๋˜์–ด ์žˆ์–ด์„œ, ํ•œ๋ฒˆ ์ต์ˆ™ํ•ด์ง€๋ฉด ๋‹ค๋ฅธ ์–ธ์–ด๋กœ ๋Œ์•„๊ฐ€๊ธฐ ์‹ซ์„ ์ •๋„์•ผ. ๊ฐ„๊ฒฐํ•˜๋ฉด์„œ๋„ ๊ฐ•๋ ฅํ•˜๊ณ , ์„ฑ๋Šฅ๋„ ๋›ฐ์–ด๋‚˜๊ฑฐ๋“ . ํŠนํžˆ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ๊ฐ€ ์ •๋ง ์‰ฌ์›Œ์„œ ๋Œ€๊ทœ๋ชจ ํŠธ๋ž˜ํ”ฝ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์„œ๋น„์Šค์— ์ตœ์ ์ด์•ผ!

ํ˜น์‹œ ๊ถ๊ธˆํ•œ ์ ์ด๋‚˜ ๋” ์•Œ๊ณ  ์‹ถ์€ ๋‚ด์šฉ์ด ์žˆ๋‹ค๋ฉด, Go ๊ณต์‹ ๋ฌธ์„œ๋‚˜ ์ปค๋ฎค๋‹ˆํ‹ฐ๋ฅผ ์ฐธ๊ณ ํ•ด๋ด. Go ์ปค๋ฎค๋‹ˆํ‹ฐ๋Š” ์ •๋ง ์นœ์ ˆํ•˜๊ณ  ํ™œ๋ฐœํ•ด์„œ ๋„์›€์„ ๋ฐ›๊ธฐ ์ข‹์•„. ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด์„œ ๋ฐฐ์šฐ๋Š” ๊ฒŒ ๊ฐ€์žฅ ๋น ๋ฅธ ๋ฐฉ๋ฒ•์ด๋‹ˆ๊นŒ, ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด๋Š” ๊ฑธ ์ถ”์ฒœํ•ด!

์ž, ์ด์ œ ๋‹น์‹ ๋„ Go HTTP ํด๋ผ์ด์–ธํŠธ ๋งˆ์Šคํ„ฐ๊ฐ€ ๋  ์ค€๋น„๊ฐ€ ๋์–ด! ํ™”์ดํŒ…! ๐Ÿš€โœจ

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

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