add req transport to fetch remote feeds
This commit is contained in:
1 parent
2424a989bc
commit
1640417cb5
2 files changed
+129
No files matched your search
@@ -0,0 +1,103 @@
|
||||
defmodule FeedReader.ReqTransport do
|
||||
@moduledoc """
|
||||
Req implementation of `FeedReader.Transport`.
|
||||
|
||||
GET responses are returned as raw, lazy chunks. Automatic Req decompression
|
||||
is disabled so the later download pipeline can enforce decoded-byte limits.
|
||||
Status 400 and 500 responses and transport failures are retried; redirect
|
||||
failures and TLS alerts are terminal. The default is five retries after the
|
||||
initial request and five redirects.
|
||||
"""
|
||||
|
||||
@behaviour FeedReader.Transport
|
||||
|
||||
@default_headers [
|
||||
{"user-agent", "feedex/1"},
|
||||
{"accept-encoding", "gzip, deflate, identity"}
|
||||
]
|
||||
|
||||
@impl true
|
||||
@spec request(String.t(), keyword()) ::
|
||||
{:ok, FeedReader.Transport.response()} | {:error, term()}
|
||||
def request(url, opts \\ [])
|
||||
|
||||
def request(url, opts) when is_binary(url) do
|
||||
http_options = Keyword.get(opts, :http_options, [])
|
||||
|
||||
request_options =
|
||||
http_options
|
||||
|> Keyword.put_new(:headers, @default_headers)
|
||||
|> Keyword.put(:into, :self)
|
||||
|> Keyword.put(:raw, true)
|
||||
|> Keyword.put(:decode_body, false)
|
||||
|> Keyword.put(:compressed, false)
|
||||
|> Keyword.put(:max_redirects, 5)
|
||||
|> Keyword.put(:max_retries, retry_attempts(opts))
|
||||
|> Keyword.put(:retry, &retry?/2)
|
||||
|> Keyword.put(:retry_delay, retry_delay(opts))
|
||||
|
||||
case Req.run([url: url], request_options) do
|
||||
{request, %Req.Response{} = response} ->
|
||||
handle_response(request, response)
|
||||
|
||||
{_request, error} ->
|
||||
{:error, error_reason(error)}
|
||||
end
|
||||
end
|
||||
|
||||
def request(_url, _opts), do: {:error, :invalid_url}
|
||||
|
||||
defp handle_response(request, response) do
|
||||
case response.status do
|
||||
status when status in 200..299 ->
|
||||
{:ok,
|
||||
%{
|
||||
status: status,
|
||||
headers: Req.get_headers_list(response),
|
||||
final_url: URI.to_string(request.url),
|
||||
body: response.body
|
||||
}}
|
||||
|
||||
403 ->
|
||||
{:error, :forbidden}
|
||||
|
||||
404 ->
|
||||
{:error, :not_found}
|
||||
|
||||
429 ->
|
||||
{:error, :too_many_requests}
|
||||
|
||||
500 ->
|
||||
{:error, :internal_server_error}
|
||||
|
||||
status ->
|
||||
{:error, {:http_status, status}}
|
||||
end
|
||||
end
|
||||
|
||||
defp retry_attempts(opts) do
|
||||
case Keyword.get(opts, :http_retry_attempts, 5) do
|
||||
attempts when is_integer(attempts) and attempts >= 0 -> attempts
|
||||
_ -> 5
|
||||
end
|
||||
end
|
||||
|
||||
defp retry_delay(opts) do
|
||||
case Keyword.get(opts, :retry_delay, 0) do
|
||||
delay when is_integer(delay) and delay >= 0 -> fn _ -> delay end
|
||||
delay when is_function(delay, 1) -> delay
|
||||
_ -> fn _ -> 0 end
|
||||
end
|
||||
end
|
||||
|
||||
defp retry?(_request, %Req.Response{status: status}), do: status in [400, 500]
|
||||
defp retry?(_request, %Req.TransportError{reason: {:tls_alert, _}}), do: false
|
||||
defp retry?(_request, %Req.TransportError{}), do: true
|
||||
defp retry?(_request, %Req.TooManyRedirectsError{}), do: false
|
||||
defp retry?(_request, _error), do: false
|
||||
|
||||
defp error_reason(%Req.TransportError{reason: {:tls_alert, {reason, _message}}}), do: reason
|
||||
defp error_reason(%Req.TransportError{reason: reason}), do: reason
|
||||
defp error_reason(%Req.TooManyRedirectsError{} = error), do: error
|
||||
defp error_reason(error), do: error
|
||||
end
|
||||
Reference in new issue
Block a user