Ver3.0 ๐ Go์ธ์ด HTTP ํด๋ผ์ด์ธํธ์ net/http ํจํค์ง๋ก ์ธ๋ถ ์๋น์ค ์๋ฒฝ ์ฐ๋ํ๊ธฐ

๐ Go์ธ์ด HTTP ํด๋ผ์ด์ธํธ์ net/http ํจํค์ง๋ก ์ธ๋ถ ์๋น์ค ์๋ฒฝ ์ฐ๋ํ๊ธฐ
์น๊ตฌ์ฒ๋ผ ์ฝ๊ฒ ๋ฐฐ์ฐ๋ Go์ HTTP ํต์ ์์ ์ ๋ณต
์๋ ! ๐ ์ค๋์ Go ์ธ์ด๋ก ์ธ๋ถ ์๋น์ค์ ํต์ ํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ด์ผ๊ธฐํด๋ณผ ๊ฑฐ์ผ. ์์ฆ ๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ์ธ๋ถ API ์ฐ๋ ์ ํ๋ ํ๋ก์ ํธ๊ฐ ๊ฑฐ์ ์์์? ๊ฒฐ์ ์์คํ , ๋ ์จ ์ ๋ณด, ์์ ๋ก๊ทธ์ธ, ์ง๋ ์๋น์ค ๋ฑ๋ฑ... ์ด ๋ชจ๋ ๊ฒ HTTP ํต์ ์ผ๋ก ์ด๋ฃจ์ด์ง๊ฑฐ๋ . ๊ทธ๋์ HTTP ํด๋ผ์ด์ธํธ๋ฅผ ์ ๋๋ก ๋ค๋ฃจ๋ ๊ฑด ์ ๋ง ์ค์ํ ์คํฌ์ด์ผ!
Go ์ธ์ด๋ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ net/http ํจํค์ง๋ฅผ ์ ๊ณตํ๋๋ฐ, ์ด๊ฒ ์ ๋ง ๊ฐ๋ ฅํด. ๋ณ๋์ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด๋ ์ฌ๋งํ HTTP ํต์ ์ ๋ค ์ฒ๋ฆฌํ ์ ์๊ฑฐ๋ . ๋ฌผ๋ก ์ฒ์์ ์ข ๋ฏ์ค ์ ์์ง๋ง, ํ๋ฒ ์ต์ํด์ง๋ฉด ์ ๋ง ํธํ๊ณ ํจ์จ์ ์ด์ผ. ๐
๐ฏ 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
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.