defmodule Frenzy.Network do require Logger @http_redirect_codes [301, 302] @spec http_get(String.t(), Keyword.t()) :: {:ok, HTTPoison.Response.t()} | {:error, term()} def http_get(url, opts \\ []) do case HTTPoison.get(url, opts) do {:ok, %HTTPoison.Response{status_code: 200} = response} -> {:ok, response} {:ok, %HTTPoison.Response{status_code: status_code, headers: headers}} when status_code in @http_redirect_codes -> headers |> Enum.find(fn {name, _value} -> name == "Location" end) |> case do {"Location", new_url} -> new_url = case URI.parse(new_url) do %URI{host: nil, path: path} -> # relative path %URI{URI.parse(url) | path: path} |> URI.to_string() uri -> uri end Logger.debug("Got 301 redirect from #{url} to #{new_url}") http_get(new_url, opts) _ -> {:error, "Missing Location header for redirect"} end {:ok, %HTTPoison.Response{status_code: 403}} -> {:error, "403 Forbidden"} {:ok, %HTTPoison.Response{status_code: 404}} -> {:error, "404 Not Found"} {:ok, %HTTPoison.Response{status_code: status_code}} -> {:error, "HTTP #{status_code}"} {:error, %HTTPoison.Error{reason: reason}} -> {:error, reason} end end @gemini_success_codes 20..29 @gemini_redirect_codes 30..39 @spec gemini_request(String.t() | URI.t()) :: {:ok, Gemini.Response.t()} | {:error, term()} def gemini_request(uri) do case Gemini.request(uri) do {:ok, %Gemini.Response{status: code} = response} when code in @gemini_success_codes -> {:ok, response} {:ok, %Gemini.Response{status: code, meta: new_url}} when code in @gemini_redirect_codes -> gemini_request(URI.merge(uri, new_url)) {:ok, %Gemini.Response{status: code}} -> {:error, "Unhandled Gemini status code: #{code}"} {:error, reason} -> {:error, reason} end end end