Dear all,
I threw together a custom step for req
that accumulates the responses from the redirects that were followed.
Basically, I re-implemented the follow_redirects
step from req
and added accumulation which is then stored to the “private part” of the response.
Would you agree that this is the proper way to go about achieving this result?
The step
defmodule MyApp.FollowStoreRedirects do
require Logger
def attach(%Req.Request{} = request) do
request
|> Req.Request.prepend_response_steps(follow_redirects: &follow_store_redirects/1)
end
@doc step: :response
defp follow_store_redirects(request_response)
defp follow_store_redirects({request, response})
when request.options.follow_redirects == false do
{request, response}
end
defp follow_store_redirects({request, %{status: status} = response})
when status in [301, 302, 303, 307, 308] do
max_redirects = Map.get(request.options, :max_redirects, 10)
redirect_count = Req.Request.get_private(request, :req_redirect_count, 0)
stored_redirects = Req.Request.get_private(request, :stored_redirects, [])
if redirect_count < max_redirects do
request =
request
|> build_redirect_request(response)
|> Req.Request.put_private(:req_redirect_count, redirect_count + 1)
|> Req.Request.put_private(:stored_redirects, [response | stored_redirects])
{_, result} = Req.Request.run(request)
{Req.Request.halt(request), result}
else
raise "too many redirects (#{max_redirects})"
end
end
defp follow_store_redirects({request, response}) do
stored_redirects = Req.Request.get_private(request, :stored_redirects, [])
{request,
response
|> Req.Response.put_private(:stored_redirects, stored_redirects)}
end
defp build_redirect_request(request, response) do
{_, location} = List.keyfind(response.headers, "location", 0)
Logger.debug(["follow_redirects: redirecting to : ", location])
location_trusted = Map.get(request.options, :location_trusted)
location_url = URI.merge(request.url, URI.parse(location))
request
|> remove_params()
|> remove_credentials_if_untrusted(location_trusted, location_url)
|> put_redirect_request_method()
|> put_redirect_location(location_url)
end
defp put_redirect_request_method(request) when request.status in 307..308, do: request
defp put_redirect_request_method(request), do: %{request | method: :get}
defp remove_credentials_if_untrusted(request, true, _), do: request
defp remove_credentials_if_untrusted(request, _, location_url) do
if {location_url.host, location_url.scheme, location_url.port} ==
{request.url.host, request.url.scheme, request.url.port} do
request
else
remove_credentials(request)
end
end
defp remove_credentials(request) do
headers = List.keydelete(request.headers, "authorization", 0)
request = update_in(request.options, &Map.delete(&1, :auth))
%{request | headers: headers}
end
defp put_redirect_location(request, location_url) do
put_in(request.url, location_url)
end
defp remove_params(request) do
update_in(request.options, &Map.delete(&1, :params))
end
end
Using the step
req =
Req.new()
|> MyApp.FollowStoreRedirects.attach()
Req.get!(req, url: "https://httpbin.org/redirect/5", max_redirects: 5)
Thank you in advance for any response and have a great rest of your day!