Skip to content

Commit faeaa15

Browse files
committed
Add x_rate_limit retry
1 parent 97b3040 commit faeaa15

File tree

1 file changed

+55
-15
lines changed

1 file changed

+55
-15
lines changed

lib/http_client/steps.ex

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,15 @@ defmodule HTTPClient.Steps do
8888
end
8989

9090
@doc """
91-
Encodes the request body based on its shape.
91+
Encodes the request body.
9292
93-
If body is of the following shape, it's encoded and its `content-type` set
94-
accordingly. Otherwise it's unchanged.
93+
## Request Options
94+
95+
* `:form` - if set, encodes the request body as form data (using `URI.encode_query/1`).
9596
96-
| Shape | Encoder | Content-Type |
97-
| --------------- | --------------------------- | ------------------------------------- |
98-
| `{:form, data}` | `URI.encode_query/1` | `"application/x-www-form-urlencoded"` |
99-
| `{:json, data}` | `Jason.encode_to_iodata!/1` | `"application/json"` |
97+
* `:json` - if set, encodes the request body as JSON (using `Jason.encode_to_iodata!/1`), sets
98+
the `accept` header to `application/json`, and the `content-type`
99+
header to `application/json`.
100100
101101
"""
102102
def encode_body(%{body: {:form, data}} = request) do
@@ -109,6 +109,7 @@ defmodule HTTPClient.Steps do
109109
request
110110
|> Map.put(:body, Jason.encode_to_iodata!(data))
111111
|> put_new_header("content-type", "application/json")
112+
|> put_new_header("accept", "application/json")
112113
end
113114

114115
def encode_body(request), do: request
@@ -339,11 +340,40 @@ defmodule HTTPClient.Steps do
339340
max_cap = get_options(retry_options, :max_cap, :timer.minutes(20))
340341
delays = cap(exponential_backoff(), max_cap)
341342
%{delay: Enum.at(delays, retry_count), retry?: true, type: :exponent}
343+
344+
:x_rate_limit ->
345+
delay = check_x_rate_limit(response_or_exception)
346+
%{delay: delay, retry?: true, type: :x_rate_limit}
347+
end
348+
end
349+
350+
defp check_x_rate_limit(%Response{headers: headers}) do
351+
case get_headers(headers, ["x-ratelimit-reset", "x-ratelimit-remaining"]) do
352+
%{"x-ratelimit-remaining" => "0", "x-ratelimit-reset" => timestamp} ->
353+
get_x_rate_limit_delay(timestamp)
354+
355+
%{"x-ratelimit-reset" => timestamp} = headers when map_size(headers) == 1 ->
356+
get_x_rate_limit_delay(timestamp)
357+
358+
_headers ->
359+
@default_retry_delay
360+
end
361+
end
362+
363+
defp check_x_rate_limit(_response_or_exception), do: @default_retry_delay
364+
365+
defp get_x_rate_limit_delay(timestamp) do
366+
with {timestamp, ""} <- Integer.parse(timestamp),
367+
{:ok, datetime} <- DateTime.from_unix(timestamp),
368+
seconds when seconds > 0 <- DateTime.diff(datetime, DateTime.utc_now()) do
369+
:timer.seconds(seconds)
370+
else
371+
_ -> @default_retry_delay
342372
end
343373
end
344374

345375
defp get_retry_delay(options, %Response{status: 429, headers: headers}) do
346-
case List.keyfind(headers, "retry-after", 0) do
376+
case get_header(headers, "retry-after", 0) do
347377
{_, header_delay} ->
348378
{:retry_after, retry_delay_in_ms(header_delay)}
349379

@@ -384,17 +414,21 @@ defmodule HTTPClient.Steps do
384414

385415
defp log_retry(response_or_exception, retry_count, retry_params) do
386416
message =
387-
cond do
388-
retry_params.type == :retry_after ->
417+
case retry_params do
418+
%{type: :retry_after} ->
389419
"Will retry after #{retry_params.delay}ms"
390420

391-
retry_params.type == :exponent ->
421+
%{type: :exponent} ->
392422
"Will retry in #{retry_params.delay}ms"
393423

394-
retry_params.max_retries - retry_count == 1 ->
424+
%{type: :x_rate_limit} ->
425+
"Will retry after #{retry_params.delay}ms"
426+
427+
%{max_retries: max_retries} when max_retries - retry_count == 1 ->
395428
"Will retry in #{retry_params.delay}ms, 1 attempt left"
396429

397-
attempts = retry_params.max_retries - retry_count ->
430+
_retry_params ->
431+
attempts = retry_params.max_retries - retry_count
398432
"Will retry in #{retry_params.delay}ms, #{attempts} attempts left"
399433
end
400434

@@ -448,8 +482,14 @@ defmodule HTTPClient.Steps do
448482
end
449483
end
450484

451-
defp get_header(headers, name) do
452-
Enum.find_value(headers, nil, fn {key, value} ->
485+
defp get_headers(headers, keys) when is_list(keys) do
486+
headers
487+
|> Keyword.take(keys)
488+
|> Map.new()
489+
end
490+
491+
defp get_header(headers, name, default_value \\ nil) do
492+
Enum.find_value(headers, default_value, fn {key, value} ->
453493
if String.downcase(key) == name, do: value
454494
end)
455495
end

0 commit comments

Comments
 (0)