GoのHTTPクライアントとミドルウェア

HTTPクライアントとミドルウェアは、マイクロサービス間の通信を支える2つの要です。一方は接続を管理し、もう一方は横断的関心事を処理します。

自分のサービスが他の5つの下流サービスを呼び出す必要がある場合、各呼び出しにはログ記録、タイムアウト処理、再試行、サーキットブレーカーが必要です。このコードを5回書くべきでしょうか、それとも一度抽象化すべきでしょうか?このレッスンでは、GoのHTTPクライアントとミドルウェアの設計パターンの高度な使い方を習得します。

1. 学習内容


2. バックエンドエンジニアの実話

(1) 課題:5つの下流サービスがあり、それぞれでログ記録、タイムアウト処理、再試行が必要である

チャーリーは、同社のAPIゲートウェイサービスの責任者を務めており、このサービスでは5つの下流サービスを呼び出す必要があります:

「下流の呼び出しはすべて、リクエストログを出力し(トラブルシューティングを容易にするため)、タイムアウトを設定し(デッドロックを防ぐため)、所要時間を記録しなければならない(監視やアラートのため)。結局、同じコードを5回も書くことになった。サービスごとに1つの関数を用意し、それぞれにログ出力、タイムアウト設定、そして呼び出し本体の3つを「サンドイッチ」のように挟んだ、同じ繰り返し処理を含める形になった。」

当時の彼のコード:

GO
// 不良コード:各下流サービスで同じロジックが繰り返される
func callUserService(w http.ResponseWriter, r *http.Request) {
    log.Printf("[%s] ユーザーサービスのリクエスト: %s", r.Method, r.URL.Path)
    start := time.Now()
    resp, err := http.Get("http://user-service/api/users")
    log.Printf("[%s] ユーザーサービスの所要時間: %v", r.Method, time.Since(start))
    // ... レスポンスの処理
}

func callOrderService(w http.ResponseWriter, r *http.Request) {
    log.Printf("[%s] 注文サービスのリクエスト: %s", r.Method, r.URL.Path)
    start := time.Now()
    resp, err := http.Get("http://order-service/api/orders")
    log.Printf("[%s] 注文処理にかかる時間: %v", r.Method, time.Since(start))
    // ... また同じパターンだ!
}
// 新しいサービスごとに繰り返される!

(2) Goによる解決策:ミドルウェアパターン+カスタムクライアント

GO
// ミドルウェア:包装 http.RoundTripper
type LoggingMiddleware struct {
    next http.RoundTripper
}

func (m *LoggingMiddleware) RoundTrip(req *http.Request) (*http.Response, エラー) {
    start := time.Now()
    log.Printf("[%s] %s %s", req.Method, req.URL.Host, req.URL.Path)
    resp, err := m.next.RoundTrip(req)
    log.Printf("[%s] %s 所要時間: %v", req.Method, req.URL.Path, time.Since(start))
    return resp, err
}

// 统一 Client:すべての下流で共有
クライアント := &http.Client{
    Timeout: 5 * time.Second,
    Transport: &LoggingMiddleware{
        next: http.DefaultTransport,
    },
}

// すべての下流呼び出しに対して自動的にログが記録される + 超时
resp1, _ := クライアント.Get("http://ユーザー-service/api/users")  // 自动日志
resp2, _ := クライアント.Get("http://順序-service/api/orders") // 自动日志

(3) メリット:ミドルウェア導入前と導入後

次元 重複コード ミドルウェアパターン
コード量 ダウンストリームごとに30行、ダウンストリームが5つ=150行 ミドルウェアが15行、合計30行
下流に追加 30行をコピーして貼り付け client.Get を直接呼び出す
編集タイムアウト 5か所を変更 1つのクライアントのタイムアウトを変更

3. HTTPクライアントの基礎

▶ サンプル:GET / POST / カスタムリクエスト

GO
package main

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

func main() {
    // 1. GET 请求
    resp, err := http.Get("https://api.example.com/users")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Printf("GET ステータスコード: %d\n", resp.StatusCode)
    fmt.Printf("応答: %s\n", body)

    // 2. POST JSON 请求
    data := map[string]string{"name": "Alice"}
    jsonData, _ := json.Marshal(data)

    resp, err = http.Post(
        "https://api.example.com/users",
        "application/json",
        bytes.NewReader(jsonData),
    )
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    // 3. カスタムリクエスト(設定 Header)
    req, _ := http.NewRequest("DELETE", "https://api.example.com/users/1", nil)
    req.Header.Set("Authorization", "Bearer token-123")
    req.Header.Set("X-Request-ID", "req-456")

    resp, err = http.DefaultClient.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    fmt.Printf("DELETE ステータスコード: %d\n", resp.StatusCode)
}
▶ 試してみよう

(1) http.Get と http.Client

GO
// 方法 1:そのまま使用 http.Get(デフォルト Client)
resp, _ := http.Get(url)
// 問題:デフォルトではタイムアウトは設定されていません,可能永远挂起!

// 方法 2:カスタマイズ Client(推荐)
client := &http.Client{
    Timeout: 10 * time.Second,
}
resp, _ := client.Get(url)
// 安全:10 タイムアウトすると自動的にキャンセルされる
メソッド タイムアウト 接続プール 推奨事項
http.Get(url) タイムアウトなし デフォルト(再利用に制限あり) ❌ 簡易テスト用のみ
http.DefaultClient タイムアウトなし デフォルト ⚠️ 本番環境では注意して使用してください
&http.Client{Timeout: 10s} グローバルタイムアウト デフォルト ✅ 推奨
&http.Client{Timeout, Transport} グローバルタイムアウト カスタム接続プール ✅ 本番環境対応

4. http.Client のタイムアウトとトランスポート

▶ サンプル:タイムアウトと接続プールの設定

GO
パッケージ main

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

func main() {
    // カスタマイズ Transport
    transport := &http.Transport{
        // 连接池
        MaxIdleConns:        100,              // 最大空き接続数
        MaxIdleConnsPerHost: 10,               // 各ホストの最大アイドル接続数
        IdleConnTimeout:     90 * time.Second, // アイドル接続のタイムアウト

        // TLS
        TLSHandshakeTimeout: 10 * time.Second,

        // 結合
        DialContext: (&net.Dialer{
            Timeout:   30 * time.Second, // 连接超时
            KeepAlive: 30 * time.Second, // Keep-Alive 間隔
        }).DialContext,
    }

    // カスタマイズ Client
    クライアント := &http.Client{
        Timeout:   30 * time.Second, // リクエストの合計タイムアウト(すべての段階を含む)
        Transport: transport,
    }

    // 使用 クライアント リクエストを送信
    resp, err := クライアント.Get("https://api.example.com/users")
    if err != nil {
        fmt.Printf("リクエストが失敗しました: %v\n", err)
        return
    }
    defer resp.Body.Close()
    fmt.Printf("ステータスコード: %d\n", resp.StatusCode)
}
▶ 試してみよう

▶ サンプル:段階的なタイムアウト制御

GO
package main

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

func main() {
    // 超時間階層化:
    // Transport.DialContext     → 连接超时(10s)
    // Transport.TLSHandshake    → TLS ハンドシェイクのタイムアウト(5s)
    // Transport.ResponseHeader  → 响应头超时(10s)
    // Client.Timeout            → 合計タイムアウト(30s,カバー Dial + TLS + 送信 + 読み込み)

    transport := &http.Transport{
        DialContext: (&net.Dialer{
            Timeout: 10 * time.Second,
        }).DialContext,
        TLSHandshakeTimeout:   5 * time.Second,
        ResponseHeaderTimeout: 10 * time.Second,
        ExpectContinueTimeout: 1 * time.Second,
    }

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

    start := time.Now()
    resp, err := client.Get("https://httpbin.org/delay/5")
    if err != nil {
        fmt.Printf("エラー: %v (所要時間: %v)\n", err, time.Since(start))
        return
    }
    defer resp.Body.Close()
    fmt.Printf("成功: %d (所要時間: %v)\n", resp.StatusCode, time.Since(start))
}
▶ 試してみよう

(2) 転送設定パラメータ

パラメータ デフォルト 推奨値 説明
MaxIdleConns 100 100–200 アイドル接続の最大数
MaxIdleConnsPerHost 2 10–50 ホストあたりのアイドル接続の最大数(デフォルトの 2 は少なすぎます!)
IdleConnTimeout 90s 30–90s アイドル接続タイムアウト
TLSHandshakeTimeout 10秒 5~10秒 TLSハンドシェイクのタイムアウト
ResponseHeaderTimeout 0 (なし) 10~30秒 レスポンスヘッダーのタイムアウト
DialContext.Timeout なし 10~30秒 TCP接続タイムアウト
💡 ヒント: http.Transport 内の MaxIdleConnsPerHost のデフォルト値は 2 のみですが、これは同じサービスへの高同時アクセスが発生するシナリオでは著しく不十分です。同時アクセス数に応じて、この値を必ず増やしてください(例:50~100)。


5. ミドルウェア・パターン

▶ サンプル:ミドルウェアのオニオンモデル

GO
package main

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

// ミドルウェアの種類:受信 Handler,戻る Handler
type Middleware func(http.Handler) http.Handler

// ミドルウェアチェーン:すべての中間ウェアを1つにまとめる
func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// ---------- 具体的なミドルウェア ----------

// 1. 日志中间件
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.RemoteAddr)
        next.ServeHTTP(w, r)
        log.Printf("[%s] %s 所要時間: %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// 2. ミドルウェアの復旧(防止 panic サービスのクラッシュを引き起こす)
func RecoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("[PANIC] %v", err)
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

// 3. CORS ミドルウェア
func CORSMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// 4. タイムアウトミドルウェア
func TimeoutMiddleware(timeout time.Duration) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctx, cancel := context.WithTimeout(r.Context(), timeout)
            defer cancel()
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// ---------- 使用 ----------

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Query().Get("name"))
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /hello", helloHandler)

    // 串联所有中间件(オニオンモデル:Recovery → Logging → CORS → Handler)
    handler := Chain(mux,
        RecoveryMiddleware,
        LoggingMiddleware,
        CORSMiddleware,
        TimeoutMiddleware(5*time.Second),
    )

    log.Print(http.ListenAndServe(":8080", handler))
}
▶ 試してみよう
100%
graph LR
    Req[请求] --> R[Recovery]
    R --> L[Logging]
    L --> C[CORS]
    C --> T[Timeout]
    T --> H[Handler]
    H -->|応答| T
    T --> C
    C --> L
    L --> R
    R --> Resp[応答]

(1) 4つの主要なミドルウェアコンポーネントの役割

ミドルウェア 役割 順序
リカバリー パニックをキャッチし、500を返す 最外層
ロギング ロギングのリクエストおよびレスポンス時間 レイヤー2
CORS クロスオリジンリクエストのヘッダーの処理 レイヤー3
タイムアウト リクエスト・タイムアウト制御 最内層(ハンドラに近い)

6. httputil.ReverseProxy リバースプロキシ

▶ サンプル:リバースプロキシ

GO
パッケージ main

import (
    "log"
    "net/http"
    "net/http/httputil"
    "net/url"
)

func main() {
    // 目标服务地址
    target, _ := url.Parse("http://localhost:8081")

    // リバースプロキシの作成
    proxy := httputil.NewSingleHostReverseProxy(target)

    // カスタマイズ Director(修改请求头)
    proxy.Director = func(req *http.Request) {
        req.URL.Scheme = target.Scheme
        req.URL.Host = target.Host
        req.URL.Path = target.Path + req.URL.Path
        req.Header.Set("X-Forwarded-Host", req.Header.Get("Host"))
        req.Header.Set("X-Real-IP", req.RemoteAddr)
    }

    // カスタムエラー処理
    proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err エラー) {
        log.Printf("プロキシエラー: %v", err)
        http.Error(w, "Bad Gateway", http.StatusBadGateway)
    }

    mux := http.NewServeMux()
    mux.HandleFunc("/api/", proxy.ServeHTTP)

    log.Print("API 网关启动于 :8080")
    log.Fatal(http.ListenAndServe(":8080", mux))
}
▶ 試してみよう
💡 ヒント: httputil.ReverseProxy は、ホストヘッダーの転送、X-Forwarded-For、レスポンスヘッダーの転送、接続プールの再利用など、プロキシに関するほとんどの処理を自動的に行います。必要なのは、リクエストを変更するための Director 関数を指定することだけです。


7. 完全な例:API Gateway + ミドルウェア・パイプライン

GO
// api_gateway.go
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "net/http/httputil"
    "net/url"
    "os"
    "os/signal"
    "strings"
    "time"
)

// ---------- 構成 ----------

type Route struct {
    Path   string
    Target *url.URL
}

type GatewayConfig struct {
    Port        string
    Routes      []Route
    Middlewares []Middleware
}

// ---------- ミドルウェア ----------

type Middleware func(http.Handler) http.Handler

func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// ログ
func Logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("[%s] %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
        log.Printf("[%s] %s → %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// 復元
func Recovery(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("[PANIC] %v", err)
                http.Error(w, `{"error":"internal error"}`, http.StatusInternalServerError)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

// CORS
func CORS(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// トラフィック制限(トークンバケツ(簡易版))
type RateLimiter struct {
    tokens chan struct{}
}

func NewRateLimiter(rate int) *RateLimiter {
    rl := &RateLimiter{tokens: make(chan struct{}, rate)}
    for i := 0; i < rate; i++ {
        rl.tokens <- struct{}{}
    }
    // 每秒补充
    go func() {
        ticker := time.NewTicker(time.Second)
        defer ticker.Stop()
        for range ticker.C {
            select {
            case rl.tokens <- struct{}{}:
            default:
            }
        }
    }()
    return rl
}

func (rl *RateLimiter) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        select {
        case <-rl.tokens:
            next.ServeHTTP(w, r)
        default:
            http.Error(w, `{"error":"rate limit exceeded"}`, http.StatusTooManyRequests)
        }
    })
}

// ---------- 网关 ----------

type Gateway struct {
    config GatewayConfig
    proxy  *httputil.ReverseProxy
}

func NewGateway(config GatewayConfig) *Gateway {
    proxy := &httputil.ReverseProxy{
        Director: func(req *http.Request) {
            // リクエストパスに基づいてルーティングをマッチングする
            for _, route := range config.Routes {
                if strings.HasPrefix(req.URL.Path, route.Path) {
                    req.URL.Scheme = route.Target.Scheme
                    req.URL.Host = route.Target.Host
                    req.URL.Path = strings.TrimPrefix(req.URL.Path, route.Path)
                    req.Header.Set("X-Forwarded-Host", req.Header.Get("Host"))
                    return
                }
            }
        },
        ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
            log.Printf("プロキシエラー: %v", err)
            http.Error(w, `{"error":"bad gateway"}`, http.StatusBadGateway)
        },
    }

    return &Gateway{config: config, proxy: proxy}
}

func (g *Gateway) Start() error {
    routesJSON, _ := json.MarshalIndent(g.config.Routes, "", "  ")
    log.Printf("ルートの読み込みを開始します:\n%s\n", routesJSON)

    // ルーティングの配布
    mux := http.NewServeMux()
    for _, route := range g.config.Routes {
        pattern := route.Path
        if !strings.HasSuffix(pattern, "/") {
            pattern += "/"
        }
        mux.Handle(pattern, g.proxy)
    }

    // 健康診断
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
    })

    // ミドルウェアチェーン
    handler := Chain(mux, g.config.Middlewares...)

    server := &http.Server{
        Addr:    ":" + g.config.Port,
        Handler: handler,
    }

    // エレガントに閉じる
    go func() {
        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh, os.Interrupt)
        <-sigCh
        log.Println("サービスを停止中です...")
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
        server.Shutdown(ctx)
    }()

    log.Printf("API 网关启动于 :%s", g.config.Port)
    return server.ListenAndServe()
}

func main() {
    userService, _ := url.Parse("http://localhost:8081")
    orderService, _ := url.Parse("http://localhost:8082")

    config := GatewayConfig{
        Port: "8080",
        Routes: []Route{
            {Path: "/api/users", Target: userService},
            {Path: "/api/orders", Target: orderService},
        },
    }

    config.Middlewares = []Middleware{
        Recovery,
        Logging,
        CORS,
    }

    gateway := NewGateway(config)
    if err := gateway.Start(); err != nil && err != http.ErrServerClosed {
        log.Fatal(err)
    }
}
🔥 よくある間違い: httputil.ReverseProxy はデフォルトで X-Forwarded-For ヘッダーを変更します。この動作を望まない場合は、Director でヘッダーを削除してください。また、ReverseProxy は WebSocket のアップグレードを自動的に処理しません。httputil.ReverseProxyServeHTTP メソッドは WebSocket をサポートしていないため、アップグレードリクエストは別途処理する必要があります。


❓ よくある質問

Q http.Gethttp.Client のどちらを選べばよいですか?
A 簡単なテストには http.Get を使用してください。本番環境では、必ずカスタム設定の http.Client を使用してください。Timeout を設定してリクエストがハングアップするのを防ぎ、Transport を設定して接続プールやタイムアウトの詳細を制御してください。http.Gethttp.DefaultClient を使用しますが、これにはデフォルトのタイムアウト設定がありません。
Q トランスポート接続プールをどのように設定すればよいですか?
A 重要なパラメータは MaxIdleConns と MaxIdleConnsPerHost です。デフォルトでは、MaxIdleConnsPerHost は 2 に設定されていますが、これは高同時接続数のサービスでは著しく不十分です。同時接続数に応じて、50~100 に設定することをお勧めします。接続をアクティブな状態に保つために、IdleConnTimeoutを30~90秒に設定してください。
Q ミドルウェアの実行順序はどのようなものですか?
A オニオンモデルでは、リクエストは最外層から入り、各層を通過してハンドラーに到達します。レスポンスは、ハンドラーから各層を通過して最外層に戻ります。登録順序によって実行順序が決まります:Chain(h, A, B, C) → リクエストは A → B → C → ハンドラーを通過し、レスポンスは C → B → A を通過します。
Q httputil.ReverseProxy はどのようなシナリオに適していますか?
A API ゲートウェイ、リバースプロキシ、およびサービスルーティングです。Host ヘッダー、X-Forwarded-For ヘッダー、および接続プールの再利用を自動的に処理します。リクエストの Scheme、Host、および Path を変更するには、Director 関数を実装するだけで済みます。マイクロサービスアーキテクチャにおけるトラフィックのエントリポイントとして適しています。
Q リクエストの再試行をどのように実装すればよいですか?
A トランスポート層でカスタムRoundTripperを実装するか、クライアント層でDoメソッドをラップしてください。再試行の冪等性について注意してください。GET、HEAD、OPTIONSなどの安全なメソッドのみが自動的に再試行されます。POST/PUTリクエストの場合、再試行する前に、ボディが再読み込み可能であることを確認する必要があります。
Q http.Client はスレッドセーフですか?
A はい。http.Client は並行して使用できます(GetDo を複数回呼び出しても、追加のロックは必要ありません)。ただし、Transport のフィールドは、最初の使用後は変更しないでください。ベストプラクティス:下流のサービスクラスタごとに1つのClientを作成し、リクエストごとに新しいClientを作成しないようにしてください。
Q リクエストのヘッダーはどのように設定すればよいですか?
A http.NewRequest を使用してリクエストを作成した後、次のようにヘッダーを設定してください:req.Header.Set("Key", "Value")req.Header.Add("Key", "Value")Addは上書きではなく追加されるため、使用しないでください。Auth、TraceID、Content-Typeなどの一般的なヘッダーは、すべてリクエストヘッダーとして渡されます。

📖 まとめ


📝 練習問題

  1. 基本演習(難易度:⭐):タイムアウトを5秒に設定したカスタム http.ClientMaxIdleConnsPerHost=20 を作成してください。この Client を使用して https://httpbin.org/delay/3 へ GET リクエストを送信し、タイムアウトが期待通りに機能することを確認してください。

  2. 上級問題(難易度 ⭐⭐)リクエスト再試行ミドルウェア(RoundTripperレイヤー)を実装してください。要件:(1) GETリクエストを最大3回まで自動的に再試行すること;(2) 再試行間隔をそれぞれ100 ms、200 ms、400 msに設定すること(指数関数的バックオフ); (3) 5xxレスポンスのみをリトライし、4xxレスポンスはリトライしないこと; (4) 各リトライの試行をログに記録すること。

  3. 課題(難易度:⭐⭐⭐)ロードバランシング機能を備えたリバースプロキシを実装してください。要件:(1) 複数のバックエンドインスタンス(例:3つのユーザーサービスインスタンス)の登録に対応すること;(2) ラウンドロビンアルゴリズムを用いてリクエストを分散させること; (3) パッシブヘルスチェックを実行すること—3回連続して失敗したインスタンスを除外し、復旧したら再度追加すること;(4) 各リクエストがルーティングされたバックエンドのアドレスをログに記録すること;(5) httputil.ReverseProxy を使用して実装すること。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%