skills/go-http-advanced/SKILL.md
Go HTTP 進階實作:Transport 重用與配置、重試策略與指數退避、Body 重播機制、 Multipart 上傳、逾時控制、HTTP Client 最佳實務、Context 傳遞。 **適用場景**:實作 HTTP Client、設計重試策略、處理 Body 重播、Multipart 檔案上傳、 配置 Connection Pool、逾時管理、Context 取消、HTTP Middleware。
npx skillsauth add vincent119/ai-rules-kit go-http-advancedInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
相關 Skills:本規範建議搭配
go-observability(Tracing)與go-graceful-shutdown
http.Clientimport (
"net/http"
"time"
)
// 建立可重用的 HTTP Client
func NewHTTPClient() *http.Client {
transport := &http.Transport{
// Connection Pool 配置
MaxIdleConns: 100, // 總最大空閒連線數
MaxIdleConnsPerHost: 10, // 每個 Host 最大空閒連線數
IdleConnTimeout: 90 * time.Second, // 空閒連線保持時間
// 連線逾時
DialContext: (&net.Dialer{
Timeout: 5 * time.Second, // TCP 連線逾時
KeepAlive: 30 * time.Second, // Keep-Alive 探測間隔
}).DialContext,
// TLS 配置
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
// HTTP/2 支援
ForceAttemptHTTP2: true,
}
return &http.Client{
Transport: transport,
Timeout: 30 * time.Second, // 全域請求逾時(包含讀取 Body)
}
}
type APIClient struct {
baseURL string
httpClient *http.Client
headers map[string]string // 預設 Headers
}
func NewAPIClient(baseURL string, httpClient *http.Client) *APIClient {
if httpClient == nil {
httpClient = NewHTTPClient()
}
return &APIClient{
baseURL: strings.TrimSuffix(baseURL, "/"),
httpClient: httpClient,
headers: map[string]string{
"User-Agent": "myapp/1.0",
},
}
}
func (c *APIClient) Get(ctx context.Context, path string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+path, nil)
if err != nil {
return nil, fmt.Errorf("new request: %w", err)
}
// 加入預設 Headers
for k, v := range c.headers {
req.Header.Set(k, v)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("do request: %w", err)
}
return resp, nil
}
原則:
func isRetryable(err error, resp *http.Response) bool {
// 網路錯誤:可重試
if err != nil {
// Timeout、DNS、Connection refused 等
return true
}
// HTTP 狀態碼判斷
switch resp.StatusCode {
case http.StatusTooManyRequests: // 429
return true
case http.StatusInternalServerError: // 500
return true
case http.StatusBadGateway: // 502
return true
case http.StatusServiceUnavailable: // 503
return true
case http.StatusGatewayTimeout: // 504
return true
default:
return false
}
}
type RetryConfig struct {
MaxRetries int
InitialBackoff time.Duration
MaxBackoff time.Duration
Multiplier float64
}
func DefaultRetryConfig() RetryConfig {
return RetryConfig{
MaxRetries: 3,
InitialBackoff: 100 * time.Millisecond,
MaxBackoff: 5 * time.Second,
Multiplier: 2.0,
}
}
func (c *APIClient) DoWithRetry(ctx context.Context, req *http.Request, cfg RetryConfig) (*http.Response, error) {
backoff := cfg.InitialBackoff
for attempt := 0; attempt <= cfg.MaxRetries; attempt++ {
// Clone Request(因為 Body 只能讀一次)
reqClone := req.Clone(ctx)
resp, err := c.httpClient.Do(reqClone)
// 成功或不可重試:直接返回
if !isRetryable(err, resp) {
return resp, err
}
// 已達最大重試次數
if attempt == cfg.MaxRetries {
if resp != nil {
return resp, fmt.Errorf("max retries exceeded, last status: %d", resp.StatusCode)
}
return nil, fmt.Errorf("max retries exceeded: %w", err)
}
// 關閉 Response Body(避免連線洩漏)
if resp != nil {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}
// Exponential Backoff
select {
case <-time.After(backoff):
backoff = time.Duration(float64(backoff) * cfg.Multiplier)
if backoff > cfg.MaxBackoff {
backoff = cfg.MaxBackoff
}
case <-ctx.Done():
return nil, ctx.Err()
}
}
return nil, fmt.Errorf("unexpected retry loop exit")
}
// ❌ 錯誤:Body 已被讀取,無法重試
req, _ := http.NewRequest("POST", url, body)
resp, err := client.Do(req)
// 若重試,body 已為空
func CloneBody(src []byte) io.ReadCloser {
buf := bytes.Clone(src)
return io.NopCloser(bytes.NewReader(buf))
}
// 可重播的 Request
func NewReplayableRequest(ctx context.Context, method, url string, body []byte) (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, method, url, CloneBody(body))
if err != nil {
return nil, err
}
// 設定 GetBody(支持 Redirect 與重試)
req.GetBody = func() (io.ReadCloser, error) {
return CloneBody(body), nil
}
return req, nil
}
func UploadFile(ctx context.Context, url string, filePath string) error {
// 1. 建立 Multipart Writer
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// 2. 新增檔案欄位
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("open file: %w", err)
}
defer file.Close()
part, err := writer.CreateFormFile("file", filepath.Base(filePath))
if err != nil {
return fmt.Errorf("create form file: %w", err)
}
if _, err := io.Copy(part, file); err != nil {
return fmt.Errorf("copy file: %w", err)
}
// 3. 新增其他欄位
_ = writer.WriteField("description", "my file")
// 4. 關閉 Writer(必要)
if err := writer.Close(); err != nil {
return fmt.Errorf("close writer: %w", err)
}
// 5. 建立 Request
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body)
if err != nil {
return fmt.Errorf("new request: %w", err)
}
// 設定 Content-Type(包含 boundary)
req.Header.Set("Content-Type", writer.FormDataContentType())
// 6. 發送請求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("upload failed: %d %s", resp.StatusCode, string(body))
}
return nil
}
func UploadLargeFile(ctx context.Context, url string, filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("open file: %w", err)
}
defer file.Close()
// 使用 io.Pipe 避免載入整個檔案到記憶體
pr, pw := io.Pipe()
writer := multipart.NewWriter(pw)
// Goroutine 寫入 Pipe
go func() {
defer pw.Close()
defer writer.Close()
part, err := writer.CreateFormFile("file", filepath.Base(filePath))
if err != nil {
pw.CloseWithError(fmt.Errorf("create form file: %w", err))
return
}
if _, err := io.Copy(part, file); err != nil {
pw.CloseWithError(fmt.Errorf("copy file: %w", err))
return
}
// 必須先關閉 writer,再關閉 pw
if err := writer.Close(); err != nil {
pw.CloseWithError(err)
}
}()
// 建立 Request(使用 Pipe Reader)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, pr)
if err != nil {
return fmt.Errorf("new request: %w", err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("upload failed: %d", resp.StatusCode)
}
return nil
}
// 1. Transport 層逾時(Connection、TLS)
transport := &http.Transport{
DialContext: (&net.Dialer{
Timeout: 5 * time.Second, // TCP 連線逾時
}).DialContext,
TLSHandshakeTimeout: 10 * time.Second,
}
// 2. Client 層逾時(整個請求,包含讀取 Body)
client := &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
// 3. Request 層逾時(Context)
ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := client.Do(req)
Context Timeout(最高優先級)> Client Timeout > Transport Timeout
type Middleware func(http.Handler) http.Handler
// Logging Middleware
func LoggingMiddleware(logger *zap.Logger) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Wrap ResponseWriter
rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(rw, r)
logger.Info("http request",
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
zap.Int("status", rw.statusCode),
zap.Duration("duration", time.Since(start)),
)
})
}
}
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
// 組合 Middleware
func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}
// 使用範例
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)
handler := Chain(mux,
LoggingMiddleware(logger),
MetricsMiddleware(),
AuthMiddleware(),
)
http.ListenAndServe(":8080", handler)
}
HTTP Client
http.Client 與 http.Transportreq.WithContext(ctx) 支持取消重試策略
Body 處理
bytes.Clone 實作 Body 重播req.GetBody 支持 Redirectdefer resp.Body.Close()Multipart 上傳
io.Pipe 串流上傳writer,再關閉 pwpw.CloseWithError(err)Content-Type(包含 boundary)Middleware
tools
基於 SLA/SLO 量化評估事故影響的計算模型與業務影響矩陣。適用於「SLA 影響」、「SLO 違反」、「影響評估」、「營收損失估算」、「Error Budget」、「可用性計算」、「事故成本評估」等量化事故業務影響的任務。強化 impact-assessor 的評估能力。注意:事故原因分析與改善規劃不在此技能範圍內。
research
根因分析(RCA)方法論詳細指南。提供 5 Whys、Fishbone 圖、Fault Tree Analysis、變更分析等結構化 RCA 技術,以及認知偏誤防範清單。適用於「根因分析」、「RCA」、「5 Whys」、「魚骨圖」、「Fault Tree」、「原因分析方法論」、「變更分析」等事故原因分析任務。強化 root-cause-investigator 的分析能力。注意:時間軸重建與改善規劃不在此技能範圍內。
testing
事故事後分析(Postmortem)完整流程。協調 7 個執行階段:資訊收集 → 時間軸重建 → 根因分析 → 影響評估 → 改善規劃 → 報告審查 → 整合報告,最終產出完整的 Postmortem 報告。適用於「寫事故報告」、「post-incident 分析」、「RCA 報告」、「事故時間軸整理」、「建立改善措施」等請求。注意:即時 Incident Response(on-call)、監控系統設定、告警配置不在此技能範圍內。
content-media
投影片版面模式庫。提供 20 種投影片類型的最佳版面配置、格線系統、色彩與字型設計 Token。適用於「投影片版面」、「Slide Layout」、「設計系統」、「格線」、「字型」、「色彩規範」等投影片視覺設計任務。強化 visual-designer 的設計能力。注意:PPT/Keynote 檔案直接輸出不在此技能範圍內。