Packages
tesla
1.10.3
1.20.0
1.18.3
1.18.2
1.18.1
1.18.0
1.17.0
1.16.0
1.15.3
1.15.2
1.15.1
1.15.0
1.14.3
1.14.2
1.14.1
1.14.0
1.13.2
1.13.1
1.13.0
1.12.3
1.12.2
1.12.1
1.12.0
1.11.2
1.11.1
1.11.0
1.10.3
1.10.2
1.10.1
1.10.0
1.9.0
1.8.1
retired
1.8.0
1.7.0
1.6.1
1.6.0
1.5.1
1.5.0
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.0
1.0.0
1.0.0-beta.1
0.10.0
0.9.0
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.2.2
0.2.1
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
HTTP client library, with support for middleware and multiple adapters.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/tesla/middleware/follow_redirects.ex
defmodule Tesla.Middleware.FollowRedirects do
@moduledoc """
Follow HTTP 3xx redirects.
## Examples
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.FollowRedirects, max_redirects: 3 # defaults to 5
end
```
## Options
- `:max_redirects` - limit number of redirects (default: `5`)
"""
@behaviour Tesla.Middleware
@max_redirects 5
@redirect_statuses [301, 302, 303, 307, 308]
@impl Tesla.Middleware
def call(env, next, opts \\ []) do
max = Keyword.get(opts || [], :max_redirects, @max_redirects)
redirect(env, next, max)
end
defp redirect(env, next, left) when left == 0 do
case Tesla.run(env, next) do
{:ok, %{status: status} = env} when status not in @redirect_statuses ->
{:ok, env}
{:ok, _env} ->
{:error, {__MODULE__, :too_many_redirects}}
error ->
error
end
end
defp redirect(env, next, left) do
case Tesla.run(env, next) do
{:ok, %{status: status} = res} when status in @redirect_statuses ->
case Tesla.get_header(res, "location") do
nil ->
{:ok, res}
location ->
prev_uri = URI.parse(env.url)
next_uri = parse_location(location, res)
# Copy opts and query params from the response env,
# these are not modified in the adapters, but middlewares
# that come after might store state there
env = %{env | opts: res.opts}
env
|> filter_headers(prev_uri, next_uri)
|> new_request(status, URI.to_string(next_uri))
|> redirect(next, left - 1)
end
other ->
other
end
end
# The 303 (See Other) redirect was added in HTTP/1.1 to indicate that the originally
# requested resource is not available, however a related resource (or another redirect)
# available via GET is available at the specified location.
# https://tools.ietf.org/html/rfc7231#section-6.4.4
defp new_request(env, 303, location), do: %{env | url: location, method: :get, query: []}
# The 307 (Temporary Redirect) status code indicates that the target
# resource resides temporarily under a different URI and the user agent
# MUST NOT change the request method (...)
# https://tools.ietf.org/html/rfc7231#section-6.4.7
defp new_request(env, 307, location), do: %{env | url: location}
defp new_request(env, _, location), do: %{env | url: location, query: []}
defp parse_location("https://" <> _rest = location, _env), do: URI.parse(location)
defp parse_location("http://" <> _rest = location, _env), do: URI.parse(location)
defp parse_location(location, env), do: env.url |> URI.parse() |> URI.merge(location)
# See https://github.com/teamon/tesla/issues/362
# See https://github.com/teamon/tesla/issues/360
@filter_headers ["authorization", "host"]
defp filter_headers(env, prev, next) do
if next.host != prev.host || next.port != prev.port || next.scheme != prev.scheme do
%{env | headers: Enum.filter(env.headers, fn {k, _} -> k not in @filter_headers end)}
else
env
end
end
end