Packages
indieweb
0.0.54
0.0.66
0.0.65
0.0.64
0.0.63
0.0.62
0.0.61
0.0.60
0.0.59
0.0.58
0.0.56
0.0.55
0.0.54
0.0.53
0.0.51
0.0.50
0.0.49
0.0.48
0.0.47
0.0.46
0.0.42
0.0.41
0.0.40
0.0.39
0.0.38
0.0.37
0.0.36
0.0.35
0.0.34
0.0.33
0.0.32
0.0.31
0.0.30
0.0.29
0.0.28
0.0.27
0.0.26
0.0.25
0.0.24
0.0.23
0.0.22
0.0.21
0.0.20
0.0.19
0.0.17
0.0.16
0.0.15
0.0.14
0.0.13
0.0.12
0.0.10
0.0.9
0.0.8
0.0.7
0.0.6
0.0.5
0.0.3
0.0.2
0.0.1
Collection of common IndieWeb utilites like authorship resolution, Webmention, post type discovery and IndieAuth.
Current section
Files
Jump to
Current section
Files
lib/indieweb/http.ex
defmodule IndieWeb.Http do
@default_retry_time 10
defmodule Error do
@moduledoc "Defines an error obtained when making a network request."
@enforce_keys ~w(message)a
@typedoc "Representative type of errors with `IndieWeb.Http`."
@type t :: %__MODULE__{message: any(), raw: any()}
defstruct ~w(message raw)a
end
defmodule Response do
@moduledoc "Defines a response obtained when making a network request."
@enforce_keys ~w(code body url headers raw)a
defstruct ~w(body code url headers rels raw)a
@type t :: %__MODULE__{
body: String.t(),
code: non_neg_integer,
url: String.t(),
headers: Access.t(),
rels: map(),
raw: any()
}
end
defmodule Client do
@moduledoc false
use Tesla
adapter(Tesla.Adapter.Hackney, timeout: 30_000)
plug(Tesla.Middleware.Logger)
plug(Tesla.Middleware.FollowRedirects)
plug(Tesla.Middleware.KeepRequest)
plug(Tesla.Middleware.MethodOverride)
plug(Tesla.Middleware.DecodeRels)
plug(Tesla.Middleware.Headers, [
{"user-agent", IndieWeb.Http.user_agent()}
])
end
def user_agent() do
version = Application.spec(:indieweb, :vsn)
"IndieWeb-Elixir/#{version} (https://git.jacky.wtf/indieweb/elixir/tree/v#{version})"
end
def make_absolute_uri(path, _) when path in ["", nil] do
path
end
def make_absolute_uri(path, base_uri)
when path == base_uri and is_binary(path) do
path
end
def make_absolute_uri(path, base_uri) when is_binary(path) do
base_uri |> URI.merge(path) |> URI.to_string()
end
def extract_link_header_values(%__MODULE__.Response{} = resp) do
resp
|> Map.get(:raw, %{})
|> Map.get(:opts, [])
|> Keyword.get(:rels, %{}) || %{}
end
def request(url, method \\ :get, opts \\ [])
def request(url, method, opts) when is_binary(url) do
case __MODULE__.Client.request([url: url, method: method] ++ opts) do
{:ok, %Tesla.Env{} = env} ->
{:ok,
%__MODULE__.Response{
raw: env,
code: env.status,
body: env.body,
rels: env.opts[:rels] || %{},
headers: env.headers,
url: env.url
}}
{:error, error} ->
{:error, %__MODULE__.Error{message: error, raw: nil}}
end
end
def request(_url, _method, _opts), do: {:error, :no_url_provided}
for method <- ~w(get post options head put patch delete)a do
@doc """
Sends a #{String.upcase(Atom.to_string(method))} request to the specified URL.
See `request/3` for more information about making requests.
"""
def unquote(method)(url, opts \\ []),
do: __MODULE__.request(url, unquote(method), opts)
end
def post_encoded(url, opts) do
post(
url,
opts ++
[
body: URI.encode_query(opts[:body]),
headers:
Keyword.get(opts, :headers, []) ++
[{"Content-Type", "application/x-www-form-urlencoded"}]
]
)
end
# TODO: Check the docs around 'Retry-After' to determine the possible formats.
@doc "Determines how many seconds from now does one have to wait to retry a request."
@spec extract_retry_time(IndieWeb.Http.Response.t()) :: integer()
def extract_retry_time(resp) do
resp.headers
|> Enum.find_value(0, fn
{"Retry-After", count} ->
if String.contains?(count, ":") do
case Calendar.DateTime.Parse.httpdate!(count) do
{:ok, retry_dt} ->
DateTime.diff(retry_dt, DateTime.utc_now(), :second)
_ ->
@default_retry_time
end
else
case Integer.parse(count) do
{seconds, _} -> seconds
_ -> @default_retry_time
end
end
_ ->
nil
end)
end
end
defimpl String.Chars, for: IndieWeb.Http.Response do
def to_string(resp) do
Jason.encode!(%{
"body" => resp.body,
"headers" => Map.new(resp.headers),
"url" => resp.url,
"code" => resp.code,
"rels" => resp.rels
})
end
end