Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Httpリクエストを自動リトライ・ポーリングするイテレータを作ってみた

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for miyamo2 miyamo2
September 04, 2024

 Httpリクエストを自動リトライ・ポーリングするイテレータを作ってみた

Avatar for miyamo2

miyamo2

September 04, 2024
Tweet

More Decks by miyamo2

Other Decks in Programming

Transcript

  1. サンプルコード url := "http://example.com" ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer

    cancel() opts := []r2.Option{ r2.WithMaxRequestAttempts(3), r2.WithPeriod(time.Second), } for resp, err := range r2.Get(ctx, url, opts...) { if err != nil { slog.WarnContext(ctx, "something happened.", slog.Any("error", err)) // Note: continueを使用してもイテレータが終了する場合がある continue } if resp == nil { slog.WarnContext(ctx, "response is nil") continue } if resp.StatusCode != http.StatusOK { slog.WarnContext(ctx, "unexpected status code.", slog.Int("expect", http.StatusOK), slog.Int("got", resp.StatusCode)) continue } buf, err := io.ReadAll(resp.Body) if err != nil { slog.ErrorContext(ctx, "failed to read response body.", slog.Any("error", err)) continue } slog.InfoContext(ctx, "response", slog.String("response", string(buf))) // r2ではデフォルトでリクエストボディが自動クローズするため明示的にクローズ処理を行う必要がない }
  2. r2がイテレータを終了する条件 リクエストが成功(ステータスコードが200 ~ 399)、 かつ WithTerminateIf が指定されていない場合 WithTerminateIf で指定された条件が満たされた場合 429:

    Too Many Requests以外の4xx クライアントエラーが返された 場合 WithMaxRequestAttempts によって指定されたリクエストの最大回数 を超えた場合 引数で渡された context.Context がキャンセルされた場合 for rangeループがbreakによって中断された場合
  3. Get 以外にr2で用意されている関数 func Head(ctx context.Context, url string, options ...Option) (*http.Response,

    error) func Post(ctx context.Context, url string, body io.Reader, options ...Option) (*http.Response, error) func Put(ctx context.Context, url string, body io.Reader, options ...Option) (*http.Response, error) func Patch(ctx context.Context, url string, body io.Reader, options ...Option) (*http.Response, error) func Delete(ctx context.Context, url string, body io.Reader, options ...Option) (*http.Response, error) func PostForm(ctx context.Context, url string, data url.Values, options ...Option) (*http.Response, error)
  4. WithPeriod func WithPeriod(period time.Duration) Option r2.WithPeriod(time.Second) 各リクエストのタイムアウト時間を指定する デフォルト、もしくは0が設定された場合はタイムアウトしない http.Client.Timeout を使用せず

    r2 独自で context.WithTimeout を用い たハンドリングを行う http.Client.Timeout と併用して同じ値が設定された場合にどちらのタ イムアウトが適用されるかの動作は未定義
  5. WithTerminateIf func WithTerminateIf(terminationCondition func(resp *http.Response, err error) bool) Option r2.WithTerminateIf(

    func(resp *http.Response, _ error) bool { buf, err := io.ReadAll(resp.Body) if err != nil { return true } data := map[string]any{} if err := json.Unmarshal(buf, data); err != nil { return false } return data["foo"] == "bar" }) ユーザー独自の終了条件を指定する レスポンスボディの巻き戻しは r2 側で対応