web-dev-qa-db-ja.com

golangを使用したすべてのリクエストでのコンテキストタイムアウトの実装

すべてのリクエストのコンテキストタイムアウトを処理しようとしています。次のサーバー構造があります。

enter image description here

フローの概要:

Goサーバー:基本的に、[リバースプロキシ]として機能します。 2

Auth Server:リクエスト認証を確認します。

Application Server:コア要求処理ロジック。

承認サーバーが規定の時間内にリクエストを処理できない場合は、メモリからgoroutineを閉じます。

これが私が試したものです:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequest("GET", authorizationServer, nil)
req.Header = r.Header
req.WithContext(ctx)
res, error := client.Do(req)
select {
case <-time.After(10 * time.Second):
    fmt.Println("overslept")
case <-ctx.Done():
    fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}

ここで、リクエストが規定された時間内に処理されない場合、コンテキストは「デッドライン超過」として返されます。しかし、それはその要求を処理し続け、指定された時間以上で応答を返します。それで、時間を超えたときにリクエストフロー(goroutine)をどのように停止できますか?.

私は完全なリクエストも実装していますが、このコードでは60秒で処理する必要があります。

var netTransport = &http.Transport{
    Dial: (&net.Dialer{
        Timeout: 60 * time.Second,
    }).Dial,
    TLSHandshakeTimeout: 60 * time.Second,
}
client := &http.Client{
    Timeout:   time.Second * 60,
    Transport: netTransport,
    CheckRedirect: func(req *http.Request, via []*http.Request) error {
        return http.ErrUseLastResponse
    },
}

それで、別のコンテキスト実装も必要ですか?助けてくれてありがとう。

注1:コンテキストを使用して、HTTPサーバーによって作成されたすべてのリクエスト(goroutine)のタイムアウトを管理できれば、素晴らしいことです。

12
Avinash

コードで何が起こるかは非常に正確で、期待どおりに動作します。

_5 seconds_タイムアウトでコンテキストを作成します。それをrequestに渡し、そのリクエストを行います。リクエストが2秒で返るとします。次に、selectを実行し、10秒待つか、コンテキストが終了するのを待ちます。コンテキストは常に、作成されたときから最初の5秒で終了し、最後に到達するたびにそのエラーも発生します。

contextはリクエストとは無関係であり、以前にキャンセルされない限り、期限に達します。関数がdeferを使用して終了すると、要求をキャンセルします。

コードでは、リクエストはタイムアウトを考慮に入れます。ただし、ctx.Err()は、タイムアウトに達するたびに_deadline exceeded_を返します。それがcontextの内部で起こっていることだからです。 ctx.Err()を複数回呼び出すと、同じエラーが返されます。

_ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

go func () {
    select {
    case <-time.After(10 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err()) // prints "context deadline exceeded"
    }
}()
req, _ := http.NewRequest("GET", authorizationServer, nil)
req.Header = r.Header
req = req.WithContext(ctx)
res, error := client.Do(req)
_

コンテキストドキュメントから:

_// Err returns a non-nil error value after Done is closed. Err returns
// Canceled if the context was canceled or DeadlineExceeded if the
// context's deadline passed. No other values for Err are defined.
// After Done is closed, successive calls to Err return the same value.
_

コードでは、タイムアウトに常に到達し、キャンセルされないため、DeadlineExceeededを受け取ります。 10秒が経過するか、コンテキストタイムアウトに達するまでブロックする選択部分を除いて、コードは正しいです。あなたのケースでは常にコンテキストのタイムアウトに達しています。

_client.Do_呼び出しによって返されたerrorを確認し、ここでのcontextエラーについて心配しないでください。あなたはコンテキストを制御する人です。リクエストがタイムアウトした場合は、もちろんテストする必要があるケースであり、確認のために適切なエラーが返されます。

15