diff options
Diffstat (limited to 'lib/pleroma/http')
| -rw-r--r-- | lib/pleroma/http/adapter_helper.ex | 121 | ||||
| -rw-r--r-- | lib/pleroma/http/adapter_helper/default.ex | 18 | ||||
| -rw-r--r-- | lib/pleroma/http/adapter_helper/gun.ex | 82 | ||||
| -rw-r--r-- | lib/pleroma/http/adapter_helper/hackney.ex | 34 | ||||
| -rw-r--r-- | lib/pleroma/http/ex_aws.ex | 24 | ||||
| -rw-r--r-- | lib/pleroma/http/request.ex | 23 | ||||
| -rw-r--r-- | lib/pleroma/http/request_builder.ex | 95 | ||||
| -rw-r--r-- | lib/pleroma/http/tzdata.ex | 29 | ||||
| -rw-r--r-- | lib/pleroma/http/web_push.ex | 12 |
9 files changed, 438 insertions, 0 deletions
diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex new file mode 100644 index 0000000..252a6ab --- /dev/null +++ b/lib/pleroma/http/adapter_helper.ex @@ -0,0 +1,121 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.AdapterHelper do + @moduledoc """ + Configure Tesla.Client with default and customized adapter options. + """ + @defaults [pool: :federation, connect_timeout: 5_000, recv_timeout: 5_000] + + @type proxy_type() :: :socks4 | :socks5 + @type host() :: charlist() | :inet.ip_address() + + alias Pleroma.HTTP.AdapterHelper + require Logger + + @type proxy :: + {Connection.host(), pos_integer()} + | {Connection.proxy_type(), Connection.host(), pos_integer()} + + @callback options(keyword(), URI.t()) :: keyword() + + @spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil + def format_proxy(nil), do: nil + + def format_proxy(proxy_url) do + case parse_proxy(proxy_url) do + {:ok, host, port} -> {host, port} + {:ok, type, host, port} -> {type, host, port} + _ -> nil + end + end + + @spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword() + def maybe_add_proxy(opts, nil), do: opts + def maybe_add_proxy(opts, proxy), do: Keyword.put_new(opts, :proxy, proxy) + + @doc """ + Merge default connection & adapter options with received ones. + """ + + @spec options(URI.t(), keyword()) :: keyword() + def options(%URI{} = uri, opts \\ []) do + @defaults + |> Keyword.merge(opts) + |> adapter_helper().options(uri) + end + + defp adapter, do: Application.get_env(:tesla, :adapter) + + defp adapter_helper do + case adapter() do + Tesla.Adapter.Gun -> AdapterHelper.Gun + Tesla.Adapter.Hackney -> AdapterHelper.Hackney + _ -> AdapterHelper.Default + end + end + + @spec parse_proxy(String.t() | tuple() | nil) :: + {:ok, host(), pos_integer()} + | {:ok, proxy_type(), host(), pos_integer()} + | {:error, atom()} + | nil + + def parse_proxy(nil), do: nil + + def parse_proxy(proxy) when is_binary(proxy) do + with [host, port] <- String.split(proxy, ":"), + {port, ""} <- Integer.parse(port) do + {:ok, parse_host(host), port} + else + {_, _} -> + Logger.warn("Parsing port failed #{inspect(proxy)}") + {:error, :invalid_proxy_port} + + :error -> + Logger.warn("Parsing port failed #{inspect(proxy)}") + {:error, :invalid_proxy_port} + + _ -> + Logger.warn("Parsing proxy failed #{inspect(proxy)}") + {:error, :invalid_proxy} + end + end + + def parse_proxy(proxy) when is_tuple(proxy) do + with {type, host, port} <- proxy do + {:ok, type, parse_host(host), port} + else + _ -> + Logger.warn("Parsing proxy failed #{inspect(proxy)}") + {:error, :invalid_proxy} + end + end + + @spec parse_host(String.t() | atom() | charlist()) :: charlist() | :inet.ip_address() + def parse_host(host) when is_list(host), do: host + def parse_host(host) when is_atom(host), do: to_charlist(host) + + def parse_host(host) when is_binary(host) do + host = to_charlist(host) + + case :inet.parse_address(host) do + {:error, :einval} -> host + {:ok, ip} -> ip + end + end + + @spec format_host(String.t()) :: charlist() + def format_host(host) do + host_charlist = to_charlist(host) + + case :inet.parse_address(host_charlist) do + {:error, :einval} -> + :idna.encode(host_charlist) + + {:ok, _ip} -> + host_charlist + end + end +end diff --git a/lib/pleroma/http/adapter_helper/default.ex b/lib/pleroma/http/adapter_helper/default.ex new file mode 100644 index 0000000..9c94147 --- /dev/null +++ b/lib/pleroma/http/adapter_helper/default.ex @@ -0,0 +1,18 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.AdapterHelper.Default do + alias Pleroma.HTTP.AdapterHelper + + @behaviour Pleroma.HTTP.AdapterHelper + + @spec options(keyword(), URI.t()) :: keyword() + def options(opts, _uri) do + proxy = Pleroma.Config.get([:http, :proxy_url], nil) + AdapterHelper.maybe_add_proxy(opts, AdapterHelper.format_proxy(proxy)) + end + + @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} + def get_conn(_uri, opts), do: {:ok, opts} +end diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex new file mode 100644 index 0000000..74ab985 --- /dev/null +++ b/lib/pleroma/http/adapter_helper/gun.ex @@ -0,0 +1,82 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.AdapterHelper.Gun do + @behaviour Pleroma.HTTP.AdapterHelper + + alias Pleroma.Config + alias Pleroma.HTTP.AdapterHelper + + require Logger + + @defaults [ + retry: 1, + retry_timeout: 1_000 + ] + + @type pool() :: :federation | :upload | :media | :default + + @spec options(keyword(), URI.t()) :: keyword() + def options(incoming_opts \\ [], %URI{} = uri) do + proxy = + [:http, :proxy_url] + |> Config.get() + |> AdapterHelper.format_proxy() + + config_opts = Config.get([:http, :adapter], []) + + @defaults + |> Keyword.merge(config_opts) + |> add_scheme_opts(uri) + |> AdapterHelper.maybe_add_proxy(proxy) + |> Keyword.merge(incoming_opts) + |> put_timeout() + end + + defp add_scheme_opts(opts, %{scheme: "http"}), do: opts + + defp add_scheme_opts(opts, %{scheme: "https"}) do + Keyword.put(opts, :certificates_verification, true) + end + + defp put_timeout(opts) do + {recv_timeout, opts} = Keyword.pop(opts, :recv_timeout, pool_timeout(opts[:pool])) + # this is the timeout to receive a message from Gun + # `:timeout` key is used in Tesla + Keyword.put(opts, :timeout, recv_timeout) + end + + @spec pool_timeout(pool()) :: non_neg_integer() + def pool_timeout(pool) do + default = Config.get([:pools, :default, :recv_timeout], 5_000) + + Config.get([:pools, pool, :recv_timeout], default) + end + + def limiter_setup do + prefix = Pleroma.Gun.ConnectionPool + wait = Config.get([:connections_pool, :connection_acquisition_wait]) + retries = Config.get([:connections_pool, :connection_acquisition_retries]) + + :pools + |> Config.get([]) + |> Enum.each(fn {name, opts} -> + max_running = Keyword.get(opts, :size, 50) + max_waiting = Keyword.get(opts, :max_waiting, 10) + + result = + ConcurrentLimiter.new(:"#{prefix}.#{name}", max_running, max_waiting, + wait: wait, + max_retries: retries + ) + + case result do + :ok -> :ok + {:error, :existing} -> :ok + end + end) + + :ok + end +end diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex new file mode 100644 index 0000000..15a84cf --- /dev/null +++ b/lib/pleroma/http/adapter_helper/hackney.ex @@ -0,0 +1,34 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.AdapterHelper.Hackney do + @behaviour Pleroma.HTTP.AdapterHelper + + @defaults [ + ] + + @spec options(keyword(), URI.t()) :: keyword() + def options(connection_opts \\ [], %URI{} = uri) do + proxy = Pleroma.Config.get([:http, :proxy_url]) + + config_opts = Pleroma.Config.get([:http, :adapter], []) + + @defaults + |> Keyword.merge(config_opts) + |> Keyword.merge(connection_opts) + |> add_scheme_opts(uri) + |> maybe_add_with_body() + |> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy) + end + + defp add_scheme_opts(opts, _), do: opts + + defp maybe_add_with_body(opts) do + if opts[:max_body] do + Keyword.put(opts, :with_body, true) + else + opts + end + end +end diff --git a/lib/pleroma/http/ex_aws.ex b/lib/pleroma/http/ex_aws.ex new file mode 100644 index 0000000..469c138 --- /dev/null +++ b/lib/pleroma/http/ex_aws.ex @@ -0,0 +1,24 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.ExAws do + @moduledoc false + + @behaviour ExAws.Request.HttpClient + + alias Pleroma.HTTP + + @impl true + def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do + http_opts = Keyword.put_new(http_opts, :pool, :upload) + + case HTTP.request(method, url, body, headers, http_opts) do + {:ok, env} -> + {:ok, %{status_code: env.status, headers: env.headers, body: env.body}} + + {:error, reason} -> + {:error, %{reason: reason}} + end + end +end diff --git a/lib/pleroma/http/request.ex b/lib/pleroma/http/request.ex new file mode 100644 index 0000000..01045f8 --- /dev/null +++ b/lib/pleroma/http/request.ex @@ -0,0 +1,23 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.Request do + @moduledoc """ + Request struct. + """ + defstruct method: :get, url: "", query: [], headers: [], body: "", opts: [] + + @type method :: :head | :get | :delete | :trace | :options | :post | :put | :patch + @type url :: String.t() + @type headers :: [{String.t(), String.t()}] + + @type t :: %__MODULE__{ + method: method(), + url: url(), + query: keyword(), + headers: headers(), + body: String.t(), + opts: keyword() + } +end diff --git a/lib/pleroma/http/request_builder.ex b/lib/pleroma/http/request_builder.ex new file mode 100644 index 0000000..f16fb3b --- /dev/null +++ b/lib/pleroma/http/request_builder.ex @@ -0,0 +1,95 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.RequestBuilder do + @moduledoc """ + Helper functions for building Tesla requests + """ + + alias Pleroma.HTTP.Request + alias Tesla.Multipart + + @doc """ + Creates new request + """ + @spec new(Request.t()) :: Request.t() + def new(%Request{} = request \\ %Request{}), do: request + + @doc """ + Specify the request method when building a request + """ + @spec method(Request.t(), Request.method()) :: Request.t() + def method(request, m), do: %{request | method: m} + + @doc """ + Specify the request method when building a request + """ + @spec url(Request.t(), Request.url()) :: Request.t() + def url(request, u), do: %{request | url: u} + + @doc """ + Add headers to the request + """ + @spec headers(Request.t(), Request.headers()) :: Request.t() + def headers(request, headers) do + headers_list = + with true <- Pleroma.Config.get([:http, :send_user_agent]), + nil <- Enum.find(headers, fn {key, _val} -> String.downcase(key) == "user-agent" end) do + [{"user-agent", Pleroma.Application.user_agent()} | headers] + else + _ -> + headers + end + + %{request | headers: headers_list} + end + + @doc """ + Add custom, per-request middleware or adapter options to the request + """ + @spec opts(Request.t(), keyword()) :: Request.t() + def opts(request, options), do: %{request | opts: options} + + @doc """ + Add optional parameters to the request + """ + @spec add_param(Request.t(), atom(), atom(), any()) :: Request.t() + def add_param(request, :query, :query, values), do: %{request | query: values} + + def add_param(request, :body, :body, value), do: %{request | body: value} + + def add_param(request, :body, key, value) do + request + |> Map.put(:body, Multipart.new()) + |> Map.update!( + :body, + &Multipart.add_field( + &1, + key, + Jason.encode!(value), + headers: [{"content-type", "application/json"}] + ) + ) + end + + def add_param(request, :file, name, path) do + request + |> Map.put(:body, Multipart.new()) + |> Map.update!(:body, &Multipart.add_file(&1, path, name: name)) + end + + def add_param(request, :form, name, value) do + Map.update(request, :body, %{name => value}, &Map.put(&1, name, value)) + end + + def add_param(request, location, key, value) do + Map.update(request, location, [{key, value}], &(&1 ++ [{key, value}])) + end + + def convert_to_keyword(request) do + request + |> Map.from_struct() + |> Enum.into([]) + end +end diff --git a/lib/pleroma/http/tzdata.ex b/lib/pleroma/http/tzdata.ex new file mode 100644 index 0000000..5d2529c --- /dev/null +++ b/lib/pleroma/http/tzdata.ex @@ -0,0 +1,29 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.Tzdata do + @moduledoc false + + @behaviour Tzdata.HTTPClient + + alias Pleroma.HTTP + + @impl true + def get(url, headers, options) do + options = Keyword.put_new(options, :pool, :default) + + with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do + {:ok, {env.status, env.headers, env.body}} + end + end + + @impl true + def head(url, headers, options) do + options = Keyword.put_new(options, :pool, :default) + + with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do + {:ok, {env.status, env.headers}} + end + end +end diff --git a/lib/pleroma/http/web_push.ex b/lib/pleroma/http/web_push.ex new file mode 100644 index 0000000..ca399b6 --- /dev/null +++ b/lib/pleroma/http/web_push.ex @@ -0,0 +1,12 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.WebPush do + @moduledoc false + + def post(url, payload, headers, options \\ []) do + list_headers = Map.to_list(headers) + Pleroma.HTTP.post(url, payload, list_headers, options) + end +end |
