aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/pleroma/application.ex30
-rw-r--r--lib/pleroma/gun/api/api.ex26
-rw-r--r--lib/pleroma/gun/api/gun.ex43
-rw-r--r--lib/pleroma/gun/api/mock.ex118
-rw-r--r--lib/pleroma/gun/conn.ex29
-rw-r--r--lib/pleroma/gun/connections.ex304
-rw-r--r--lib/pleroma/http/connection.ex112
-rw-r--r--lib/pleroma/http/http.ex59
-rw-r--r--lib/pleroma/http/request_builder.ex2
-rw-r--r--lib/pleroma/object/fetcher.ex6
-rw-r--r--lib/pleroma/reverse_proxy/client.ex17
-rw-r--r--lib/pleroma/reverse_proxy/client/tesla.ex60
-rw-r--r--lib/pleroma/reverse_proxy/reverse_proxy.ex18
-rw-r--r--lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex4
-rw-r--r--lib/pleroma/web/admin_api/config.ex6
-rw-r--r--lib/pleroma/web/ostatus/ostatus.ex2
-rw-r--r--lib/pleroma/web/rel_me.ex4
-rw-r--r--lib/pleroma/web/rich_media/parser.ex4
-rw-r--r--lib/pleroma/web/web_finger/web_finger.ex2
19 files changed, 758 insertions, 88 deletions
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 483ac1f39..00d89f4c4 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -39,7 +39,7 @@ defmodule Pleroma.Application do
Pleroma.ActivityExpirationWorker
] ++
cachex_children() ++
- hackney_pool_children() ++
+ gun_pools() ++
[
Pleroma.Web.Federator.RetryQueue,
Pleroma.Stats,
@@ -95,20 +95,6 @@ defmodule Pleroma.Application do
Pleroma.Web.Endpoint.Instrumenter.setup()
end
- def enabled_hackney_pools do
- [:media] ++
- if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
- [:federation]
- else
- []
- end ++
- if Pleroma.Config.get([Pleroma.Upload, :proxy_remote]) do
- [:upload]
- else
- []
- end
- end
-
defp cachex_children do
[
build_cachex("used_captcha", ttl_interval: seconds_valid_interval()),
@@ -157,10 +143,16 @@ defmodule Pleroma.Application do
defp chat_child(_, _), do: []
- defp hackney_pool_children do
- for pool <- enabled_hackney_pools() do
- options = Pleroma.Config.get([:hackney_pools, pool])
- :hackney_pool.child_spec(pool, options)
+ defp gun_pools do
+ if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun || Mix.env() == :test do
+ for {pool_name, opts} <- Pleroma.Config.get([:gun_pools]) do
+ %{
+ id: :"gun_pool_#{pool_name}",
+ start: {Pleroma.Gun.Connections, :start_link, [{pool_name, opts}]}
+ }
+ end
+ else
+ []
end
end
diff --git a/lib/pleroma/gun/api/api.ex b/lib/pleroma/gun/api/api.ex
new file mode 100644
index 000000000..43ee7f354
--- /dev/null
+++ b/lib/pleroma/gun/api/api.ex
@@ -0,0 +1,26 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Gun.API do
+ @callback open(charlist(), pos_integer(), map()) :: {:ok, pid()}
+ @callback info(pid()) :: map()
+ @callback close(pid()) :: :ok
+ @callback await_up(pid) :: {:ok, atom()} | {:error, atom()}
+ @callback connect(pid(), map()) :: reference()
+ @callback await(pid(), reference()) :: {:response, :fin, 200, []}
+
+ def open(host, port, opts), do: api().open(host, port, opts)
+
+ def info(pid), do: api().info(pid)
+
+ def close(pid), do: api().close(pid)
+
+ def await_up(pid), do: api().await_up(pid)
+
+ def connect(pid, opts), do: api().connect(pid, opts)
+
+ def await(pid, ref), do: api().await(pid, ref)
+
+ defp api, do: Pleroma.Config.get([Pleroma.Gun.API], Pleroma.Gun.API.Gun)
+end
diff --git a/lib/pleroma/gun/api/gun.ex b/lib/pleroma/gun/api/gun.ex
new file mode 100644
index 000000000..603dd700e
--- /dev/null
+++ b/lib/pleroma/gun/api/gun.ex
@@ -0,0 +1,43 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Gun.API.Gun do
+ @behaviour Pleroma.Gun.API
+
+ alias Pleroma.Gun.API
+
+ @gun_keys [
+ :connect_timeout,
+ :http_opts,
+ :http2_opts,
+ :protocols,
+ :retry,
+ :retry_timeout,
+ :trace,
+ :transport,
+ :tls_opts,
+ :tcp_opts,
+ :ws_opts
+ ]
+
+ @impl API
+ def open(host, port, opts) do
+ :gun.open(host, port, Map.take(opts, @gun_keys))
+ end
+
+ @impl API
+ def info(pid), do: :gun.info(pid)
+
+ @impl API
+ def close(pid), do: :gun.close(pid)
+
+ @impl API
+ def await_up(pid), do: :gun.await_up(pid)
+
+ @impl API
+ def connect(pid, opts), do: :gun.connect(pid, opts)
+
+ @impl API
+ def await(pid, ref), do: :gun.await(pid, ref)
+end
diff --git a/lib/pleroma/gun/api/mock.ex b/lib/pleroma/gun/api/mock.ex
new file mode 100644
index 000000000..5e1bb8abc
--- /dev/null
+++ b/lib/pleroma/gun/api/mock.ex
@@ -0,0 +1,118 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Gun.API.Mock do
+ @behaviour Pleroma.Gun.API
+
+ alias Pleroma.Gun.API
+
+ @impl API
+ def open(domain, 80, %{genserver_pid: genserver_pid})
+ when domain in ['another-domain.com', 'some-domain.com'] do
+ {:ok, conn_pid} = Task.start_link(fn -> Process.sleep(1_000) end)
+
+ Registry.register(API.Mock, conn_pid, %{
+ origin_scheme: "http",
+ origin_host: domain,
+ origin_port: 80
+ })
+
+ send(genserver_pid, {:gun_up, conn_pid, :http})
+ {:ok, conn_pid}
+ end
+
+ @impl API
+ def open('some-domain.com', 443, %{genserver_pid: genserver_pid}) do
+ {:ok, conn_pid} = Task.start_link(fn -> Process.sleep(1_000) end)
+
+ Registry.register(API.Mock, conn_pid, %{
+ origin_scheme: "https",
+ origin_host: 'some-domain.com',
+ origin_port: 443
+ })
+
+ send(genserver_pid, {:gun_up, conn_pid, :http2})
+ {:ok, conn_pid}
+ end
+
+ @impl API
+ def open('gun_down.com', 80, %{genserver_pid: genserver_pid}) do
+ {:ok, conn_pid} = Task.start_link(fn -> Process.sleep(1_000) end)
+
+ Registry.register(API.Mock, conn_pid, %{
+ origin_scheme: "http",
+ origin_host: 'gun_down.com',
+ origin_port: 80
+ })
+
+ send(genserver_pid, {:gun_down, conn_pid, :http, nil, nil, nil})
+ {:ok, conn_pid}
+ end
+
+ @impl API
+ def open('gun_down_and_up.com', 80, %{genserver_pid: genserver_pid}) do
+ {:ok, conn_pid} = Task.start_link(fn -> Process.sleep(1_000) end)
+
+ Registry.register(API.Mock, conn_pid, %{
+ origin_scheme: "http",
+ origin_host: 'gun_down_and_up.com',
+ origin_port: 80
+ })
+
+ send(genserver_pid, {:gun_down, conn_pid, :http, nil, nil, nil})
+
+ {:ok, _} =
+ Task.start_link(fn ->
+ Process.sleep(500)
+
+ send(genserver_pid, {:gun_up, conn_pid, :http})
+ end)
+
+ {:ok, conn_pid}
+ end
+
+ @impl API
+ def open({127, 0, 0, 1}, 8123, _) do
+ Task.start_link(fn -> Process.sleep(1_000) end)
+ end
+
+ @impl API
+ def open('localhost', 9050, _) do
+ Task.start_link(fn -> Process.sleep(1_000) end)
+ end
+
+ @impl API
+ def await_up(_pid) do
+ {:ok, :http}
+ end
+
+ @impl API
+ def connect(pid, %{host: _, port: 80}) do
+ ref = make_ref()
+ Registry.register(API.Mock, ref, pid)
+ ref
+ end
+
+ @impl API
+ def connect(pid, %{host: _, port: 443, protocols: [:http2], transport: :tls}) do
+ ref = make_ref()
+ Registry.register(API.Mock, ref, pid)
+ ref
+ end
+
+ @impl API
+ def await(pid, ref) do
+ [{_, ^pid}] = Registry.lookup(API.Mock, ref)
+ {:response, :fin, 200, []}
+ end
+
+ @impl API
+ def info(pid) do
+ [{_, info}] = Registry.lookup(API.Mock, pid)
+ info
+ end
+
+ @impl API
+ def close(_pid), do: :ok
+end
diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex
new file mode 100644
index 000000000..906607b28
--- /dev/null
+++ b/lib/pleroma/gun/conn.ex
@@ -0,0 +1,29 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Gun.Conn do
+ @moduledoc """
+ Struct for gun connection data
+ """
+ @type gun_state :: :open | :up | :down
+ @type conn_state :: :init | :active | :idle
+
+ @type t :: %__MODULE__{
+ conn: pid(),
+ gun_state: gun_state(),
+ waiting_pids: [pid()],
+ conn_state: conn_state(),
+ used_by: [pid()],
+ last_reference: pos_integer(),
+ crf: float()
+ }
+
+ defstruct conn: nil,
+ gun_state: :open,
+ waiting_pids: [],
+ conn_state: :init,
+ used_by: [],
+ last_reference: :os.system_time(:second),
+ crf: 1
+end
diff --git a/lib/pleroma/gun/connections.ex b/lib/pleroma/gun/connections.ex
new file mode 100644
index 000000000..e3d392de7
--- /dev/null
+++ b/lib/pleroma/gun/connections.ex
@@ -0,0 +1,304 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Gun.Connections do
+ use GenServer
+ require Logger
+
+ @type domain :: String.t()
+ @type conn :: Pleroma.Gun.Conn.t()
+
+ @type t :: %__MODULE__{
+ conns: %{domain() => conn()},
+ opts: keyword()
+ }
+
+ defstruct conns: %{}, opts: [], queue: []
+
+ alias Pleroma.Gun.API
+ alias Pleroma.Gun.Conn
+
+ @spec start_link({atom(), keyword()}) :: {:ok, pid()} | :ignore
+ def start_link({name, opts}) do
+ GenServer.start_link(__MODULE__, opts, name: name)
+ end
+
+ @impl true
+ def init(opts), do: {:ok, %__MODULE__{conns: %{}, opts: opts}}
+
+ @spec checkin(String.t(), keyword(), atom()) :: pid()
+ def checkin(url, opts \\ [], name \\ :default) do
+ opts = Enum.into(opts, %{})
+
+ uri = URI.parse(url)
+
+ opts =
+ if uri.scheme == "https" and uri.port != 443,
+ do: Map.put(opts, :transport, :tls),
+ else: opts
+
+ opts =
+ if uri.scheme == "https" do
+ host = uri.host |> to_charlist()
+
+ tls_opts =
+ Map.get(opts, :tls_opts, [])
+ |> Keyword.put(:server_name_indication, host)
+
+ Map.put(opts, :tls_opts, tls_opts)
+ else
+ opts
+ end
+
+ GenServer.call(
+ name,
+ {:checkin, %{opts: opts, uri: uri}}
+ )
+ end
+
+ @spec alive?(atom()) :: boolean()
+ def alive?(name \\ :default) do
+ pid = Process.whereis(name)
+ if pid, do: Process.alive?(pid), else: false
+ end
+
+ @spec get_state(atom()) :: t()
+ def get_state(name \\ :default) do
+ GenServer.call(name, {:state})
+ end
+
+ def checkout(conn, pid, name \\ :default) do
+ GenServer.cast(name, {:checkout, conn, pid})
+ end
+
+ def process_queue(name \\ :default) do
+ GenServer.cast(name, {:process_queue})
+ end
+
+ @impl true
+ def handle_cast({:checkout, conn_pid, pid}, state) do
+ {key, conn} = find_conn(state.conns, conn_pid)
+ used_by = List.keydelete(conn.used_by, pid, 0)
+ conn_state = if used_by == [], do: :idle, else: conn.conn_state
+ state = put_in(state.conns[key], %{conn | conn_state: conn_state, used_by: used_by})
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_cast({:process_queue}, state) do
+ case state.queue do
+ [{from, key, uri, opts} | _queue] ->
+ try_to_checkin(key, uri, from, state, Map.put(opts, :from_cast, true))
+
+ [] ->
+ {:noreply, state}
+ end
+ end
+
+ @impl true
+ def handle_call({:checkin, %{opts: opts, uri: uri}}, from, state) do
+ key = compose_key(uri)
+
+ case state.conns[key] do
+ %{conn: conn, gun_state: gun_state} = current_conn when gun_state == :up ->
+ time = current_time()
+ last_reference = time - current_conn.last_reference
+
+ current_crf = crf(last_reference, 100, current_conn.crf)
+
+ state =
+ put_in(state.conns[key], %{
+ current_conn
+ | last_reference: time,
+ crf: current_crf,
+ conn_state: :active,
+ used_by: [from | current_conn.used_by]
+ })
+
+ {:reply, conn, state}
+
+ %{gun_state: gun_state, waiting_pids: pids} when gun_state in [:open, :down] ->
+ state = put_in(state.conns[key].waiting_pids, [from | pids])
+ {:noreply, state}
+
+ nil ->
+ max_connections = state.opts[:max_connections]
+
+ if Enum.count(state.conns) < max_connections do
+ open_conn(key, uri, from, state, opts)
+ else
+ try_to_checkin(key, uri, from, state, opts)
+ end
+ end
+ end
+
+ @impl true
+ def handle_call({:state}, _from, state), do: {:reply, state, state}
+
+ defp try_to_checkin(key, uri, from, state, opts) do
+ unused_conns =
+ state.conns
+ |> Enum.filter(fn {_k, v} ->
+ v.conn_state == :idle and v.waiting_pids == [] and v.used_by == []
+ end)
+ |> Enum.sort(fn {_x_k, x}, {_y_k, y} ->
+ x.crf < y.crf and x.last_reference < y.last_reference
+ end)
+
+ case unused_conns do
+ [{close_key, least_used} | _conns] ->
+ :ok = API.close(least_used.conn)
+
+ state =
+ put_in(
+ state.conns,
+ Map.delete(state.conns, close_key)
+ )
+
+ open_conn(key, uri, from, state, opts)
+
+ [] ->
+ queue =
+ if List.keymember?(state.queue, from, 0),
+ do: state.queue,
+ else: state.queue ++ [{from, key, uri, opts}]
+
+ state = put_in(state.queue, queue)
+ {:noreply, state}
+ end
+ end
+
+ @impl true
+ def handle_info({:gun_up, conn_pid, _protocol}, state) do
+ conn_key = compose_key_gun_info(conn_pid)
+ {key, conn} = find_conn(state.conns, conn_pid, conn_key)
+
+ # Update state of the current connection and set waiting_pids to empty list
+ time = current_time()
+ last_reference = time - conn.last_reference
+ current_crf = crf(last_reference, 100, conn.crf)
+
+ state =
+ put_in(state.conns[key], %{
+ conn
+ | gun_state: :up,
+ waiting_pids: [],
+ last_reference: time,
+ crf: current_crf,
+ conn_state: :active,
+ used_by: conn.waiting_pids ++ conn.used_by
+ })
+
+ # Send to all waiting processes connection pid
+ Enum.each(conn.waiting_pids, fn waiting_pid -> GenServer.reply(waiting_pid, conn_pid) end)
+
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info({:gun_down, conn_pid, _protocol, _reason, _killed, _unprocessed}, state) do
+ # we can't get info on this pid, because pid is dead
+ {key, conn} = find_conn(state.conns, conn_pid)
+
+ Enum.each(conn.waiting_pids, fn waiting_pid -> GenServer.reply(waiting_pid, nil) end)
+
+ state = put_in(state.conns[key].gun_state, :down)
+ {:noreply, state}
+ end
+
+ defp compose_key(uri), do: "#{uri.scheme}:#{uri.host}:#{uri.port}"
+
+ defp compose_key_gun_info(pid) do
+ info = API.info(pid)
+ "#{info.origin_scheme}:#{info.origin_host}:#{info.origin_port}"
+ end
+
+ defp find_conn(conns, conn_pid) do
+ Enum.find(conns, fn {_key, conn} ->
+ conn.conn == conn_pid
+ end)
+ end
+
+ defp find_conn(conns, conn_pid, conn_key) do
+ Enum.find(conns, fn {key, conn} ->
+ key == conn_key and conn.conn == conn_pid
+ end)
+ end
+
+ defp open_conn(key, uri, from, state, %{proxy: {proxy_host, proxy_port}} = opts) do
+ host = to_charlist(uri.host)
+ port = uri.port
+
+ tls_opts = Map.get(opts, :tls_opts, [])
+ connect_opts = %{host: host, port: port}
+
+ connect_opts =
+ if uri.scheme == "https" do
+ Map.put(connect_opts, :protocols, [:http2])
+ |> Map.put(:transport, :tls)
+ |> Map.put(:tls_opts, tls_opts)
+ else
+ connect_opts
+ end
+
+ with open_opts <- Map.delete(opts, :tls_opts),
+ {:ok, conn} <- API.open(proxy_host, proxy_port, open_opts),
+ {:ok, _} <- API.await_up(conn),
+ stream <- API.connect(conn, connect_opts),
+ {:response, :fin, 200, _} <- API.await(conn, stream) do
+ state =
+ put_in(state.conns[key], %Conn{
+ conn: conn,
+ waiting_pids: [],
+ gun_state: :up,
+ conn_state: :active,
+ used_by: [from]
+ })
+
+ if opts[:from_cast] do
+ GenServer.reply(from, conn)
+ end
+
+ {:reply, conn, state}
+ else
+ error ->
+ Logger.warn(inspect(error))
+ {:reply, nil, state}
+ end
+ end
+
+ defp open_conn(key, uri, from, state, opts) do
+ host = to_charlist(uri.host)
+ port = uri.port
+
+ with {:ok, conn} <- API.open(host, port, opts) do
+ state =
+ if opts[:from_cast] do
+ put_in(state.queue, List.keydelete(state.queue, from, 0))
+ else
+ state
+ end
+
+ state =
+ put_in(state.conns[key], %Conn{
+ conn: conn,
+ waiting_pids: [from]
+ })
+
+ {:noreply, state}
+ else
+ error ->
+ Logger.warn(inspect(error))
+ {:reply, nil, state}
+ end
+ end
+
+ defp current_time do
+ :os.system_time(:second)
+ end
+
+ def crf(current, steps, crf) do
+ 1 + :math.pow(0.5, current / steps) * crf
+ end
+end
diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex
index 7e2c6f5e8..d4e6d0f99 100644
--- a/lib/pleroma/http/connection.ex
+++ b/lib/pleroma/http/connection.ex
@@ -7,14 +7,13 @@ defmodule Pleroma.HTTP.Connection do
Connection for http-requests.
"""
- @hackney_options [
+ @options [
connect_timeout: 10_000,
- recv_timeout: 20_000,
- follow_redirect: true,
- force_redirect: true,
+ timeout: 20_000,
pool: :federation
]
- @adapter Application.get_env(:tesla, :adapter)
+
+ require Logger
@doc """
Configure a client connection
@@ -25,19 +24,108 @@ defmodule Pleroma.HTTP.Connection do
"""
@spec new(Keyword.t()) :: Tesla.Env.client()
def new(opts \\ []) do
- Tesla.client([], {@adapter, hackney_options(opts)})
+ middleware = [Tesla.Middleware.FollowRedirects]
+ adapter = Application.get_env(:tesla, :adapter)
+ Tesla.client(middleware, {adapter, options(opts)})
end
- # fetch Hackney options
+ # fetch http options
#
- def hackney_options(opts) do
+ def options(opts) do
options = Keyword.get(opts, :adapter, [])
adapter_options = Pleroma.Config.get([:http, :adapter], [])
+
proxy_url = Pleroma.Config.get([:http, :proxy_url], nil)
- @hackney_options
- |> Keyword.merge(adapter_options)
- |> Keyword.merge(options)
- |> Keyword.merge(proxy: proxy_url)
+ proxy =
+ case parse_proxy(proxy_url) do
+ {:ok, proxy_host, proxy_port} -> {proxy_host, proxy_port}
+ _ -> nil
+ end
+
+ options =
+ @options
+ |> Keyword.merge(adapter_options)
+ |> Keyword.merge(options)
+ |> Keyword.merge(proxy: proxy)
+
+ pool = options[:pool]
+ url = options[:url]
+
+ if not is_nil(url) and not is_nil(pool) and Pleroma.Gun.Connections.alive?(pool) do
+ get_conn_for_gun(url, options, pool)
+ else
+ options
+ end
+ end
+
+ defp get_conn_for_gun(url, options, pool) do
+ case Pleroma.Gun.Connections.checkin(url, options, pool) do
+ nil ->
+ options
+
+ conn ->
+ %{host: host, port: port} = URI.parse(url)
+
+ # verify sertificates opts for gun
+ tls_opts = [
+ verify: :verify_peer,
+ cacerts: :certifi.cacerts(),
+ depth: 20,
+ server_name_indication: to_charlist(host),
+ reuse_sessions: false,
+ verify_fun: {&:ssl_verify_hostname.verify_fun/3, [check_hostname: to_charlist(host)]}
+ ]
+
+ Keyword.put(options, :conn, conn)
+ |> Keyword.put(:close_conn, false)
+ |> Keyword.put(:original, "#{host}:#{port}")
+ |> Keyword.put(:tls_opts, tls_opts)
+ end
+ end
+
+ @spec parse_proxy(String.t() | tuple() | nil) ::
+ {tuple, 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 in proxy fail #{inspect(proxy)}")
+ {:error, :error_parsing_port_in_proxy}
+
+ :error ->
+ Logger.warn("parsing port in proxy fail #{inspect(proxy)}")
+ {:error, :error_parsing_port_in_proxy}
+
+ _ ->
+ Logger.warn("parsing proxy fail #{inspect(proxy)}")
+ {:error, :error_parsing_proxy}
+ end
+ end
+
+ def parse_proxy(proxy) when is_tuple(proxy) do
+ with {_type, host, port} <- proxy do
+ {:ok, parse_host(host), port}
+ else
+ _ ->
+ Logger.warn("parsing proxy fail #{inspect(proxy)}")
+ {:error, :error_parsing_proxy}
+ end
+ end
+
+ @spec parse_host(String.t() | tuple()) :: charlist() | atom()
+ 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
end
diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex
index dec24458a..0a7db737f 100644
--- a/lib/pleroma/http/http.ex
+++ b/lib/pleroma/http/http.ex
@@ -28,21 +28,44 @@ defmodule Pleroma.HTTP do
"""
def request(method, url, body \\ "", headers \\ [], options \\ []) do
try do
+ options = process_request_options(options)
+
+ adapter_gun? = Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun
+
options =
- process_request_options(options)
- |> process_sni_options(url)
+ if adapter_gun? do
+ adapter_opts =
+ Keyword.get(options, :adapter, [])
+ |> Keyword.put(:url, url)
+
+ Keyword.put(options, :adapter, adapter_opts)
+ else
+ options
+ end
params = Keyword.get(options, :params, [])
- %{}
- |> Builder.method(method)
- |> Builder.headers(headers)
- |> Builder.opts(options)
- |> Builder.url(url)
- |> Builder.add_param(:body, :body, body)
- |> Builder.add_param(:query, :query, params)
- |> Enum.into([])
- |> (&Tesla.request(Connection.new(options), &1)).()
+ request =
+ %{}
+ |> Builder.method(method)
+ |> Builder.url(url)
+ |> Builder.headers(headers)
+ |> Builder.opts(options)
+ |> Builder.add_param(:body, :body, body)
+ |> Builder.add_param(:query, :query, params)
+ |> Enum.into([])
+
+ client = Connection.new(options)
+ response = Tesla.request(client, request)
+
+ if adapter_gun? do
+ %{adapter: {_, _, [adapter_options]}} = client
+ pool = adapter_options[:pool]
+ Pleroma.Gun.Connections.checkout(adapter_options[:conn], self(), pool)
+ Pleroma.Gun.Connections.process_queue(pool)
+ end
+
+ response
rescue
e ->
{:error, e}
@@ -52,20 +75,8 @@ defmodule Pleroma.HTTP do
end
end
- defp process_sni_options(options, nil), do: options
-
- defp process_sni_options(options, url) do
- uri = URI.parse(url)
- host = uri.host |> to_charlist()
-
- case uri.scheme do
- "https" -> options ++ [ssl: [server_name_indication: host]]
- _ -> options
- end
- end
-
def process_request_options(options) do
- Keyword.merge(Pleroma.HTTP.Connection.hackney_options([]), options)
+ Keyword.merge(Pleroma.HTTP.Connection.options([]), options)
end
@doc """
diff --git a/lib/pleroma/http/request_builder.ex b/lib/pleroma/http/request_builder.ex
index e23457999..4e77870bd 100644
--- a/lib/pleroma/http/request_builder.ex
+++ b/lib/pleroma/http/request_builder.ex
@@ -48,7 +48,7 @@ defmodule Pleroma.HTTP.RequestBuilder do
def headers(request, header_list) do
header_list =
if Pleroma.Config.get([:http, :send_user_agent]) do
- header_list ++ [{"User-Agent", Pleroma.Application.user_agent()}]
+ header_list ++ [{"user-agent", Pleroma.Application.user_agent()}]
else
header_list
end
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index c1795ae0f..1dfba04eb 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -95,7 +95,7 @@ defmodule Pleroma.Object.Fetcher do
date: date
})
- [{:Signature, signature}]
+ [{"signature", signature}]
end
defp sign_fetch(headers, id, date) do
@@ -108,7 +108,7 @@ defmodule Pleroma.Object.Fetcher do
defp maybe_date_fetch(headers, date) do
if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
- headers ++ [{:Date, date}]
+ headers ++ [{"date", date}]
else
headers
end
@@ -120,7 +120,7 @@ defmodule Pleroma.Object.Fetcher do
date = Pleroma.Signature.signed_date()
headers =
- [{:Accept, "application/activity+json"}]
+ [{"accept", "application/activity+json"}]
|> maybe_date_fetch(date)
|> sign_fetch(id, date)
diff --git a/lib/pleroma/reverse_proxy/client.ex b/lib/pleroma/reverse_proxy/client.ex
index 776c4794c..71c2b2911 100644
--- a/lib/pleroma/reverse_proxy/client.ex
+++ b/lib/pleroma/reverse_proxy/client.ex
@@ -3,9 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReverseProxy.Client do
- @callback request(atom(), String.t(), [tuple()], String.t(), list()) ::
- {:ok, pos_integer(), [tuple()], reference() | map()}
- | {:ok, pos_integer(), [tuple()]}
+ @type status :: pos_integer()
+ @type header_name :: String.t()
+ @type header_value :: String.t()
+ @type headers :: [{header_name(), header_value()}]
+
+ @callback request(atom(), String.t(), headers(), String.t(), list()) ::
+ {:ok, status(), headers(), reference() | map()}
+ | {:ok, status(), headers()}
| {:ok, reference()}
| {:error, term()}
@@ -14,8 +19,8 @@ defmodule Pleroma.ReverseProxy.Client do
@callback close(reference() | pid() | map()) :: :ok
- def request(method, url, headers, "", opts \\ []) do
- client().request(method, url, headers, "", opts)
+ def request(method, url, headers, body \\ "", opts \\ []) do
+ client().request(method, url, headers, body, opts)
end
def stream_body(ref), do: client().stream_body(ref)
@@ -23,6 +28,6 @@ defmodule Pleroma.ReverseProxy.Client do
def close(ref), do: client().close(ref)
defp client do
- Pleroma.Config.get([Pleroma.ReverseProxy.Client], :hackney)
+ Pleroma.Config.get([Pleroma.ReverseProxy.Client], Pleroma.ReverseProxy.Client.Tesla)
end
end
diff --git a/lib/pleroma/reverse_proxy/client/tesla.ex b/lib/pleroma/reverse_proxy/client/tesla.ex
new file mode 100644
index 000000000..fad577ec1
--- /dev/null
+++ b/lib/pleroma/reverse_proxy/client/tesla.ex
@@ -0,0 +1,60 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-onl
+
+defmodule Pleroma.ReverseProxy.Client.Tesla do
+ @behaviour Pleroma.ReverseProxy.Client
+
+ @adapters [Tesla.Adapter.Gun]
+
+ def request(method, url, headers, body, opts \\ []) do
+ adapter_opts =
+ Keyword.get(opts, :adapter, [])
+ |> Keyword.put(:body_as, :chunks)
+
+ with {:ok, response} <-
+ Pleroma.HTTP.request(
+ method,
+ url,
+ body,
+ headers,
+ Keyword.put(opts, :adapter, adapter_opts)
+ ) do
+ if is_map(response.body),
+ do: {:ok, response.status, response.headers, response.body},
+ else: {:ok, response.status, response.headers}
+ else
+ {:error, error} -> {:error, error}
+ end
+ end
+
+ def stream_body(%{fin: true}), do: :done
+
+ def stream_body(client) do
+ case read_chunk!(client) do
+ {:fin, body} -> {:ok, body, Map.put(client, :fin, true)}
+ {:nofin, part} -> {:ok, part, client}
+ {:error, error} -> {:error, error}
+ end
+ end
+
+ defp read_chunk!(%{pid: pid, stream: stream, opts: opts}) do
+ adapter = Application.get_env(:tesla, :adapter)
+
+ unless adapter in @adapters do
+ raise "#{adapter} doesn't support reading body in chunks"
+ end
+
+ adapter.read_chunk(pid, stream, opts)
+ end
+
+ def close(pid) do
+ adapter = Application.get_env(:tesla, :adapter)
+
+ unless adapter in @adapters do
+ raise "#{adapter} doesn't support closing connection"
+ end
+
+ adapter.close(pid)
+ end
+end
diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex
index 03efad30a..df4eca207 100644
--- a/lib/pleroma/reverse_proxy/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex
@@ -58,10 +58,10 @@ defmodule Pleroma.ReverseProxy do
* `req_headers`, `resp_headers` additional headers.
- * `http`: options for [hackney](https://github.com/benoitc/hackney).
+ * `http`: options for [gun](https://github.com/ninenines/gun).
"""
- @default_hackney_options [pool: :media]
+ @default_options [pool: :media]
@inline_content_types [
"image/gif",
@@ -93,9 +93,9 @@ defmodule Pleroma.ReverseProxy do
def call(_conn, _url, _opts \\ [])
def call(conn = %{method: method}, url, opts) when method in @methods do
- hackney_opts =
- Pleroma.HTTP.Connection.hackney_options([])
- |> Keyword.merge(@default_hackney_options)
+ client_opts =
+ Pleroma.HTTP.Connection.options([])
+ |> Keyword.merge(@default_options)
|> Keyword.merge(Keyword.get(opts, :http, []))
|> HTTP.process_request_options()
@@ -108,7 +108,7 @@ defmodule Pleroma.ReverseProxy do
opts
end
- with {:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts),
+ with {:ok, code, headers, client} <- request(method, url, req_headers, client_opts),
:ok <-
header_length_constraint(
headers,
@@ -147,11 +147,11 @@ defmodule Pleroma.ReverseProxy do
|> halt()
end
- defp request(method, url, headers, hackney_opts) do
+ defp request(method, url, headers, opts) do
Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}")
method = method |> String.downcase() |> String.to_existing_atom()
- case client().request(method, url, headers, "", hackney_opts) do
+ case client().request(method, url, headers, "", opts) do
{:ok, code, headers, client} when code in @valid_resp_codes ->
{:ok, code, downcase_headers(headers), client}
@@ -201,7 +201,7 @@ defmodule Pleroma.ReverseProxy do
duration,
Keyword.get(opts, :max_read_duration, @max_read_duration)
),
- {:ok, data} <- client().stream_body(client),
+ {:ok, data, client} <- client().stream_body(client),
{:ok, duration} <- increase_read_duration(duration),
sent_so_far = sent_so_far + byte_size(data),
:ok <-
diff --git a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex
index a179dd54d..52ef0167c 100644
--- a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex
@@ -11,7 +11,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
require Logger
- @hackney_options [
+ @options [
pool: :media,
recv_timeout: 10_000
]
@@ -21,7 +21,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
url
|> MediaProxy.url()
- |> HTTP.get([], adapter: @hackney_options)
+ |> HTTP.get([], adapter: @options)
end
def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do
diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex
index a10cc779b..25e91eee6 100644
--- a/lib/pleroma/web/admin_api/config.ex
+++ b/lib/pleroma/web/admin_api/config.ex
@@ -95,7 +95,6 @@ defmodule Pleroma.Web.AdminAPI.Config do
end
defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]}
- defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]}
defp do_convert(entity) when is_tuple(entity),
do: %{"tuple" => do_convert(Tuple.to_list(entity))}
@@ -129,11 +128,6 @@ defmodule Pleroma.Web.AdminAPI.Config do
{:dispatch, [dispatch_settings]}
end
- defp do_transform(%{"tuple" => [":partial_chain", entity]}) do
- {partial_chain, []} = do_eval(entity)
- {:partial_chain, partial_chain}
- end
-
defp do_transform(%{"tuple" => entity}) do
Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
end
diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex
index 331cbc0b7..1c400d9e4 100644
--- a/lib/pleroma/web/ostatus/ostatus.ex
+++ b/lib/pleroma/web/ostatus/ostatus.ex
@@ -373,7 +373,7 @@ defmodule Pleroma.Web.OStatus do
{:ok, %{body: body, status: code}} when code in 200..299 <-
HTTP.get(
url,
- [{:Accept, "application/atom+xml"}]
+ [{"accept", "application/atom+xml"}]
) do
Logger.debug("Got document from #{url}, handling...")
handle_incoming(body, options)
diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex
index d376e2069..947234aa2 100644
--- a/lib/pleroma/web/rel_me.ex
+++ b/lib/pleroma/web/rel_me.ex
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RelMe do
- @hackney_options [
+ @options [
pool: :media,
recv_timeout: 2_000,
max_body: 2_000_000,
@@ -25,7 +25,7 @@ defmodule Pleroma.Web.RelMe do
def parse(_), do: {:error, "No URL provided"}
defp parse_url(url) do
- {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
+ {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @options)
data =
Floki.attribute(html, "link[rel~=me]", "href") ++
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index f5f9e358c..ade4ac891 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parser do
- @hackney_options [
+ @options [
pool: :media,
recv_timeout: 2_000,
max_body: 2_000_000,
@@ -78,7 +78,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
defp parse_url(url) do
try do
- {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
+ {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @options)
html
|> maybe_parse()
diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex
index ecb39ee50..624ee5ef7 100644
--- a/lib/pleroma/web/web_finger/web_finger.ex
+++ b/lib/pleroma/web/web_finger/web_finger.ex
@@ -217,7 +217,7 @@ defmodule Pleroma.Web.WebFinger do
with response <-
HTTP.get(
address,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
),
{:ok, %{status: status, body: body}} when status in 200..299 <- response do
doc = XML.parse_document(body)