aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAlexander Strizhakov <alex.strizhakov@gmail.com>2020-04-29 10:31:41 +0300
committerAlexander Strizhakov <alex.strizhakov@gmail.com>2020-05-06 13:25:48 +0300
commit73b1b3ff5e2761c62738e85ea1a6f822c718be2f (patch)
tree818a78f43b96bce51ea55b77db1aba26c9895b75 /lib
parent5ca7b9f6908cf3ec3cfb68bbaffcf72e85361b41 (diff)
downloadpleroma-73b1b3ff5e2761c62738e85ea1a6f822c718be2f.tar.gz
fixing connection leaks
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/pleroma/benchmark.ex4
-rw-r--r--lib/pleroma/gun/conn.ex13
-rw-r--r--lib/pleroma/http/adapter_helper.ex41
-rw-r--r--lib/pleroma/http/adapter_helper/gun.ex82
-rw-r--r--lib/pleroma/http/adapter_helper/hackney.ex43
-rw-r--r--lib/pleroma/http/connection.ex103
-rw-r--r--lib/pleroma/http/gun.ex65
-rw-r--r--lib/pleroma/http/hackney.ex49
-rw-r--r--lib/pleroma/http/http.ex41
-rw-r--r--lib/pleroma/http/middleware/follow_redirects.ex109
-rw-r--r--lib/pleroma/http/proxy.ex67
-rw-r--r--lib/pleroma/http/request/builder.ex (renamed from lib/pleroma/http/request_builder.ex)4
-rw-r--r--lib/pleroma/http/request/request.ex (renamed from lib/pleroma/http/request.ex)0
-rw-r--r--lib/pleroma/reverse_proxy/client/tesla.ex12
14 files changed, 328 insertions, 305 deletions
diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex
index 6ab7fe8ef..3d37dfa24 100644
--- a/lib/mix/tasks/pleroma/benchmark.ex
+++ b/lib/mix/tasks/pleroma/benchmark.ex
@@ -92,13 +92,13 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
"Without conn and without pool" => fn ->
{:ok, %Tesla.Env{}} =
Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [],
- adapter: [pool: :no_pool, receive_conn: false]
+ adapter: [pool: :no_pool, reuse_conn: false]
)
end,
"Without conn and with pool" => fn ->
{:ok, %Tesla.Env{}} =
Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [],
- adapter: [receive_conn: false]
+ adapter: [reuse_conn: false]
)
end,
"With reused conn and without pool" => fn ->
diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex
index 4c2f5b1bf..52b80cc85 100644
--- a/lib/pleroma/gun/conn.ex
+++ b/lib/pleroma/gun/conn.ex
@@ -72,14 +72,17 @@ defmodule Pleroma.Gun.Conn do
defp maybe_add_tls_opts(opts, %URI{scheme: "http"}), do: opts
defp maybe_add_tls_opts(opts, %URI{scheme: "https", host: host}) do
+ charlist_host =
+ host
+ |> to_charlist()
+ |> :idna.encode()
+
tls_opts = [
verify: :verify_peer,
cacertfile: CAStore.file_path(),
depth: 20,
reuse_sessions: false,
- verify_fun:
- {&:ssl_verify_hostname.verify_fun/3,
- [check_hostname: Pleroma.HTTP.Connection.format_host(host)]}
+ verify_fun: {&:ssl_verify_hostname.verify_fun/3, [check_hostname: charlist_host]}
]
tls_opts =
@@ -153,7 +156,7 @@ defmodule Pleroma.Gun.Conn do
end
defp do_open(%URI{host: host, port: port} = uri, opts) do
- host = Pleroma.HTTP.Connection.parse_host(host)
+ host = Pleroma.HTTP.Connection.format_host(host)
with {:ok, conn} <- Gun.open(host, port, opts),
{:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do
@@ -169,7 +172,7 @@ defmodule Pleroma.Gun.Conn do
end
defp destination_opts(%URI{host: host, port: port}) do
- host = Pleroma.HTTP.Connection.parse_host(host)
+ host = Pleroma.HTTP.Connection.format_host(host)
%{host: host, port: port}
end
diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex
deleted file mode 100644
index 510722ff9..000000000
--- a/lib/pleroma/http/adapter_helper.ex
+++ /dev/null
@@ -1,41 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.HTTP.AdapterHelper do
- alias Pleroma.HTTP.Connection
-
- @type proxy ::
- {Connection.host(), pos_integer()}
- | {Connection.proxy_type(), Connection.host(), pos_integer()}
-
- @callback options(keyword(), URI.t()) :: keyword()
- @callback after_request(keyword()) :: :ok
-
- @spec options(keyword(), URI.t()) :: keyword()
- def options(opts, _uri) do
- proxy = Pleroma.Config.get([:http, :proxy_url], nil)
- maybe_add_proxy(opts, format_proxy(proxy))
- end
-
- @spec maybe_get_conn(URI.t(), keyword()) :: keyword()
- def maybe_get_conn(_uri, opts), do: opts
-
- @spec after_request(keyword()) :: :ok
- def after_request(_opts), do: :ok
-
- @spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil
- def format_proxy(nil), do: nil
-
- def format_proxy(proxy_url) do
- case Connection.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)
-end
diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex
deleted file mode 100644
index bdf2bcc06..000000000
--- a/lib/pleroma/http/adapter_helper/gun.ex
+++ /dev/null
@@ -1,82 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.HTTP.AdapterHelper.Gun do
- @behaviour Pleroma.HTTP.AdapterHelper
-
- alias Pleroma.HTTP.AdapterHelper
- alias Pleroma.Pool.Connections
-
- require Logger
-
- @defaults [
- connect_timeout: 5_000,
- domain_lookup_timeout: 5_000,
- tls_handshake_timeout: 5_000,
- retry: 1,
- retry_timeout: 1000,
- await_up_timeout: 5_000
- ]
-
- @spec options(keyword(), URI.t()) :: keyword()
- def options(incoming_opts \\ [], %URI{} = uri) do
- proxy =
- Pleroma.Config.get([:http, :proxy_url])
- |> AdapterHelper.format_proxy()
-
- config_opts = Pleroma.Config.get([:http, :adapter], [])
-
- @defaults
- |> Keyword.merge(config_opts)
- |> add_scheme_opts(uri)
- |> AdapterHelper.maybe_add_proxy(proxy)
- |> maybe_get_conn(uri, incoming_opts)
- end
-
- @spec after_request(keyword()) :: :ok
- def after_request(opts) do
- if opts[:conn] && opts[:body_as] != :chunks do
- Connections.checkout(opts[:conn], self(), :gun_connections)
- end
-
- :ok
- end
-
- defp add_scheme_opts(opts, %{scheme: "http"}), do: opts
-
- defp add_scheme_opts(opts, %{scheme: "https"}) do
- tls_opts = [
- log_level: :warning,
- session_lifetime: 6000,
- session_cache_client_max: 250
- ]
-
- opts
- |> Keyword.put(:certificates_verification, true)
- |> Keyword.put(:tls_opts, tls_opts)
- end
-
- defp maybe_get_conn(adapter_opts, uri, incoming_opts) do
- {receive_conn?, opts} =
- adapter_opts
- |> Keyword.merge(incoming_opts)
- |> Keyword.pop(:receive_conn, true)
-
- if Connections.alive?(:gun_connections) and receive_conn? do
- checkin_conn(uri, opts)
- else
- opts
- end
- end
-
- defp checkin_conn(uri, opts) do
- case Connections.checkin(uri, :gun_connections) do
- nil ->
- opts
-
- conn when is_pid(conn) ->
- Keyword.merge(opts, conn: conn, close_conn: false)
- end
- end
-end
diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex
deleted file mode 100644
index dcb4cac71..000000000
--- a/lib/pleroma/http/adapter_helper/hackney.ex
+++ /dev/null
@@ -1,43 +0,0 @@
-defmodule Pleroma.HTTP.AdapterHelper.Hackney do
- @behaviour Pleroma.HTTP.AdapterHelper
-
- @defaults [
- connect_timeout: 10_000,
- recv_timeout: 20_000,
- follow_redirect: true,
- force_redirect: true,
- pool: :federation
- ]
-
- @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)
- |> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy)
- end
-
- defp add_scheme_opts(opts, %URI{scheme: "http"}), do: opts
-
- defp add_scheme_opts(opts, %URI{scheme: "https", host: host}) do
- ssl_opts = [
- ssl_options: [
- # Workaround for remote server certificate chain issues
- partial_chain: &:hackney_connect.partial_chain/1,
-
- # We don't support TLS v1.3 yet
- versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
- server_name_indication: to_charlist(host)
- ]
- ]
-
- Keyword.merge(opts, ssl_opts)
- end
-
- def after_request(_), do: :ok
-end
diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex
index ebacf7902..f5c1432fa 100644
--- a/lib/pleroma/http/connection.ex
+++ b/lib/pleroma/http/connection.ex
@@ -7,100 +7,31 @@ defmodule Pleroma.HTTP.Connection do
Configure Tesla.Client with default and customized adapter options.
"""
- alias Pleroma.Config
- alias Pleroma.HTTP.AdapterHelper
-
require Logger
- @defaults [pool: :federation]
-
- @type ip_address :: ipv4_address() | ipv6_address()
@type ipv4_address :: {0..255, 0..255, 0..255, 0..255}
@type ipv6_address ::
{0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535}
- @type proxy_type() :: :socks4 | :socks5
- @type host() :: charlist() | ip_address()
+ @type host() :: charlist() | ipv4_address() | ipv6_address()
@doc """
- Merge default connection & adapter options with received ones.
+ Merge default connection & adapter options with received options.
"""
@spec options(URI.t(), keyword()) :: keyword()
def options(%URI{} = uri, opts \\ []) do
- @defaults
- |> pool_timeout()
- |> Keyword.merge(opts)
- |> adapter_helper().options(uri)
- end
-
- defp pool_timeout(opts) do
- {config_key, default} =
- if adapter() == Tesla.Adapter.Gun do
- {:pools, Config.get([:pools, :default, :timeout])}
- else
- {:hackney_pools, 10_000}
- end
-
- timeout = Config.get([config_key, opts[:pool], :timeout], default)
-
- Keyword.merge(opts, timeout: timeout)
- end
-
- @spec after_request(keyword()) :: :ok
- def after_request(opts), do: adapter_helper().after_request(opts)
-
- 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
- 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
+ adapter = Application.get_env(:tesla, :adapter)
- 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
+ [pool: :federation]
+ |> Keyword.merge(opts)
+ |> adapter_options(uri, adapter)
end
- @spec parse_host(String.t() | atom() | charlist()) :: charlist() | ip_address()
- def parse_host(host) when is_list(host), do: host
- def parse_host(host) when is_atom(host), do: to_charlist(host)
+ @spec format_host(String.t() | atom() | charlist()) :: host()
+ def format_host(host) when is_list(host), do: host
+ def format_host(host) when is_atom(host), do: to_charlist(host)
- def parse_host(host) when is_binary(host) do
+ def format_host(host) when is_binary(host) do
host = to_charlist(host)
case :inet.parse_address(host) do
@@ -109,16 +40,10 @@ defmodule Pleroma.HTTP.Connection do
end
end
- @spec format_host(String.t()) :: charlist()
- def format_host(host) do
- host_charlist = to_charlist(host)
+ defp adapter_options(opts, uri, Tesla.Adapter.Gun), do: Pleroma.HTTP.Gun.options(opts, uri)
- case :inet.parse_address(host_charlist) do
- {:error, :einval} ->
- :idna.encode(host_charlist)
+ defp adapter_options(opts, uri, Tesla.Adapter.Hackney),
+ do: Pleroma.HTTP.Hackney.options(opts, uri)
- {:ok, _ip} ->
- host_charlist
- end
- end
+ defp adapter_options(opts, _, _), do: Keyword.put(opts, :env, Pleroma.Config.get(:env))
end
diff --git a/lib/pleroma/http/gun.ex b/lib/pleroma/http/gun.ex
new file mode 100644
index 000000000..eeaa22c51
--- /dev/null
+++ b/lib/pleroma/http/gun.ex
@@ -0,0 +1,65 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.Gun do
+ alias Pleroma.Config
+
+ @spec options(keyword(), URI.t()) :: keyword()
+ def options(opts \\ [], %URI{} = uri) do
+ merge_with_defaults()
+ |> add_scheme_opts(uri)
+ |> maybe_add_proxy()
+ |> Keyword.merge(opts)
+ |> add_pool_timeout()
+ |> add_reuse_conn_flag()
+ |> add_pool_alive_flag()
+ end
+
+ defp merge_with_defaults do
+ config = Config.get([:http, :adapter], [])
+
+ defaults = [
+ connect_timeout: 5_000,
+ domain_lookup_timeout: 5_000,
+ tls_handshake_timeout: 5_000,
+ retry: 1,
+ retry_timeout: 1000,
+ await_up_timeout: 5_000
+ ]
+
+ Keyword.merge(defaults, config)
+ end
+
+ defp add_scheme_opts(opts, %{scheme: "http"}), do: opts
+
+ defp add_scheme_opts(opts, %{scheme: "https"}) do
+ tls_opts = [
+ log_level: :warning,
+ session_lifetime: 6000,
+ session_cache_client_max: 250
+ ]
+
+ Keyword.merge(opts, certificates_verification: true, tls_opts: tls_opts)
+ end
+
+ defp maybe_add_proxy(opts), do: Pleroma.HTTP.Proxy.maybe_add_proxy(opts)
+
+ defp add_pool_timeout(opts) do
+ default_timeout = Config.get([:pools, :default, :timeout])
+ timeout = Config.get([:pools, opts[:pool], :timeout], default_timeout)
+ Keyword.put(opts, :timeout, timeout)
+ end
+
+ defp add_reuse_conn_flag(opts) do
+ Keyword.update(opts, :reuse_conn, true, fn flag? ->
+ Pleroma.Pool.Connections.alive?(:gun_connections) and flag?
+ end)
+ end
+
+ defp add_pool_alive_flag(opts) do
+ pid = Process.whereis(opts[:pool])
+ pool_alive? = !is_nil(pid) && Process.alive?(pid)
+ Keyword.put(opts, :pool_alive?, pool_alive?)
+ end
+end
diff --git a/lib/pleroma/http/hackney.ex b/lib/pleroma/http/hackney.ex
new file mode 100644
index 000000000..782e8b5ae
--- /dev/null
+++ b/lib/pleroma/http/hackney.ex
@@ -0,0 +1,49 @@
+defmodule Pleroma.HTTP.Hackney do
+ @spec options(keyword(), URI.t()) :: keyword()
+ def options(opts \\ [], %URI{} = uri) do
+ merge_with_defaults()
+ |> add_scheme_opts(uri)
+ |> maybe_add_proxy()
+ |> merge_with_incoming_opts(opts)
+ |> add_pool_timeout()
+ end
+
+ defp merge_with_defaults do
+ config = Pleroma.Config.get([:http, :adapter], [])
+
+ defaults = [
+ connect_timeout: 10_000,
+ recv_timeout: 20_000,
+ follow_redirect: true,
+ force_redirect: true
+ ]
+
+ Keyword.merge(defaults, config)
+ end
+
+ defp add_scheme_opts(opts, %URI{scheme: "http"}), do: opts
+
+ defp add_scheme_opts(opts, %URI{scheme: "https", host: host}) do
+ ssl_opts = [
+ ssl_options: [
+ # Workaround for remote server certificate chain issues
+ partial_chain: &:hackney_connect.partial_chain/1,
+
+ # We don't support TLS v1.3 yet
+ versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
+ server_name_indication: to_charlist(host)
+ ]
+ ]
+
+ Keyword.merge(opts, ssl_opts)
+ end
+
+ defp maybe_add_proxy(opts), do: Pleroma.HTTP.Proxy.maybe_add_proxy(opts)
+
+ defp merge_with_incoming_opts(opts, incoming), do: Keyword.merge(opts, incoming)
+
+ defp add_pool_timeout(opts) do
+ timeout = Pleroma.Config.get([:hackney_pools, opts[:pool], :timeout], 10_000)
+ Keyword.put(opts, :timeout, timeout)
+ end
+end
diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex
index 583b56484..2a182b81e 100644
--- a/lib/pleroma/http/http.ex
+++ b/lib/pleroma/http/http.ex
@@ -7,9 +7,8 @@ defmodule Pleroma.HTTP do
Wrapper for `Tesla.request/2`.
"""
- alias Pleroma.HTTP.Connection
alias Pleroma.HTTP.Request
- alias Pleroma.HTTP.RequestBuilder, as: Builder
+ alias Pleroma.HTTP.Request.Builder
alias Tesla.Client
alias Tesla.Env
@@ -56,44 +55,25 @@ defmodule Pleroma.HTTP do
{:ok, Env.t()} | {:error, any()}
def request(method, url, body, headers, options) when is_binary(url) do
uri = URI.parse(url)
- adapter_opts = Connection.options(uri, options[:adapter] || [])
+ adapter_opts = Pleroma.HTTP.Connection.options(uri, options[:adapter] || [])
options = put_in(options[:adapter], adapter_opts)
- params = options[:params] || []
- request = build_request(method, headers, options, url, body, params)
adapter = Application.get_env(:tesla, :adapter)
- client = Tesla.client([Tesla.Middleware.FollowRedirects], adapter)
- pid = Process.whereis(adapter_opts[:pool])
+ client = Tesla.client([Pleroma.HTTP.Middleware.FollowRedirects], adapter)
+ request = build_request(method, headers, options, url, body)
- pool_alive? =
- if adapter == Tesla.Adapter.Gun && pid do
- Process.alive?(pid)
- else
- false
- end
-
- request_opts =
- adapter_opts
- |> Enum.into(%{})
- |> Map.put(:env, Pleroma.Config.get([:env]))
- |> Map.put(:pool_alive?, pool_alive?)
-
- response = request(client, request, request_opts)
-
- Connection.after_request(adapter_opts)
-
- response
+ request(client, request, Enum.into(adapter_opts, %{}))
end
@spec request(Client.t(), keyword(), map()) :: {:ok, Env.t()} | {:error, any()}
- def request(%Client{} = client, request, %{env: :test}), do: request(client, request)
+ def request(client, request, %{env: :test}), do: request(client, request)
- def request(%Client{} = client, request, %{body_as: :chunks}), do: request(client, request)
+ def request(client, request, %{body_as: :chunks}), do: request(client, request)
- def request(%Client{} = client, request, %{pool_alive?: false}), do: request(client, request)
+ def request(client, request, %{pool_alive?: false}), do: request(client, request)
- def request(%Client{} = client, request, %{pool: pool, timeout: timeout}) do
+ def request(client, request, %{pool: pool, timeout: timeout}) do
:poolboy.transaction(
pool,
&Pleroma.Pool.Request.execute(&1, client, request, timeout),
@@ -104,14 +84,13 @@ defmodule Pleroma.HTTP do
@spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()}
def request(client, request), do: Tesla.request(client, request)
- defp build_request(method, headers, options, url, body, params) do
+ defp build_request(method, headers, options, url, body) do
Builder.new()
|> Builder.method(method)
|> Builder.headers(headers)
|> Builder.opts(options)
|> Builder.url(url)
|> Builder.add_param(:body, :body, body)
- |> Builder.add_param(:query, :query, params)
|> Builder.convert_to_keyword()
end
end
diff --git a/lib/pleroma/http/middleware/follow_redirects.ex b/lib/pleroma/http/middleware/follow_redirects.ex
new file mode 100644
index 000000000..bb22504c7
--- /dev/null
+++ b/lib/pleroma/http/middleware/follow_redirects.ex
@@ -0,0 +1,109 @@
+defmodule Pleroma.HTTP.Middleware.FollowRedirects do
+ @moduledoc """
+ Follow 3xx redirects
+ ## Example
+ ```
+ 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) do
+ opts = env.opts[:adapter]
+
+ adapter_opts =
+ if opts[:reuse_conn] do
+ checkin_conn(env.url, opts)
+ else
+ opts
+ end
+
+ env = %{env | opts: Keyword.put(env.opts, :adapter, adapter_opts)}
+
+ case Tesla.run(env, next) do
+ {:ok, %{status: status} = res} when status in @redirect_statuses and left > 0 ->
+ checkout_conn(adapter_opts)
+
+ case Tesla.get_header(res, "location") do
+ nil ->
+ {:ok, res}
+
+ location ->
+ location = parse_location(location, res)
+
+ env
+ |> new_request(res.status, location)
+ |> redirect(next, left - 1)
+ end
+
+ {:ok, %{status: status}} when status in @redirect_statuses ->
+ checkout_conn(adapter_opts)
+ {:error, {__MODULE__, :too_many_redirects}}
+
+ other ->
+ unless adapter_opts[:body_as] == :chunks do
+ checkout_conn(adapter_opts)
+ end
+
+ other
+ end
+ end
+
+ defp checkin_conn(url, opts) do
+ uri = URI.parse(url)
+
+ case Pleroma.Pool.Connections.checkin(uri, :gun_connections, opts) do
+ nil ->
+ opts
+
+ conn when is_pid(conn) ->
+ Keyword.merge(opts, conn: conn, close_conn: false)
+ end
+ end
+
+ defp checkout_conn(opts) do
+ if is_pid(opts[:conn]) do
+ Pleroma.Pool.Connections.checkout(opts[:conn], self(), :gun_connections)
+ 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: location
+ defp parse_location("http://" <> _rest = location, _env), do: location
+
+ defp parse_location(location, env) do
+ env.url
+ |> URI.parse()
+ |> URI.merge(location)
+ |> URI.to_string()
+ end
+end
diff --git a/lib/pleroma/http/proxy.ex b/lib/pleroma/http/proxy.ex
new file mode 100644
index 000000000..aa9964120
--- /dev/null
+++ b/lib/pleroma/http/proxy.ex
@@ -0,0 +1,67 @@
+defmodule Pleroma.HTTP.Proxy do
+ require Logger
+
+ alias Pleroma.HTTP.Connection
+
+ @type proxy_type() :: :socks4 | :socks5
+
+ @spec parse_proxy(String.t() | tuple() | nil) ::
+ {:ok, Connection.host(), pos_integer()}
+ | {:ok, proxy_type(), Connection.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, Connection.format_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, Connection.format_host(host), port}
+ else
+ _ ->
+ Logger.warn("Parsing proxy failed #{inspect(proxy)}")
+ {:error, :invalid_proxy}
+ end
+ end
+
+ defp format_proxy(nil), do: nil
+
+ defp 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()) :: keyword()
+ def maybe_add_proxy(opts) do
+ proxy =
+ Pleroma.Config.get([:http, :proxy_url])
+ |> format_proxy()
+
+ if proxy do
+ Keyword.put_new(opts, :proxy, proxy)
+ else
+ opts
+ end
+ end
+end
diff --git a/lib/pleroma/http/request_builder.ex b/lib/pleroma/http/request/builder.ex
index 2fc876d92..50bc4e3ad 100644
--- a/lib/pleroma/http/request_builder.ex
+++ b/lib/pleroma/http/request/builder.ex
@@ -2,7 +2,7 @@
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.HTTP.RequestBuilder do
+defmodule Pleroma.HTTP.Request.Builder do
@moduledoc """
Helper functions for building Tesla requests
"""
@@ -53,8 +53,6 @@ defmodule Pleroma.HTTP.RequestBuilder do
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
diff --git a/lib/pleroma/http/request.ex b/lib/pleroma/http/request/request.ex
index 761bd6ccf..761bd6ccf 100644
--- a/lib/pleroma/http/request.ex
+++ b/lib/pleroma/http/request/request.ex
diff --git a/lib/pleroma/reverse_proxy/client/tesla.ex b/lib/pleroma/reverse_proxy/client/tesla.ex
index e81ea8bde..999b1a4cb 100644
--- a/lib/pleroma/reverse_proxy/client/tesla.ex
+++ b/lib/pleroma/reverse_proxy/client/tesla.ex
@@ -26,7 +26,7 @@ defmodule Pleroma.ReverseProxy.Client.Tesla do
url,
body,
headers,
- Keyword.put(opts, :adapter, opts)
+ adapter: opts
) do
if is_map(response.body) and method != :head do
{:ok, response.status, response.headers, response.body}
@@ -41,14 +41,8 @@ defmodule Pleroma.ReverseProxy.Client.Tesla do
@impl true
@spec stream_body(map()) ::
{:ok, binary(), map()} | {:error, atom() | String.t()} | :done | no_return()
- def stream_body(%{pid: pid, opts: opts, fin: true}) do
- # if connection was reused, but in tesla were redirects,
- # tesla returns new opened connection, which must be closed manually
- if opts[:old_conn], do: Tesla.Adapter.Gun.close(pid)
- # if there were redirects we need to checkout old conn
- conn = opts[:old_conn] || opts[:conn]
-
- if conn, do: :ok = Pleroma.Pool.Connections.checkout(conn, self(), :gun_connections)
+ def stream_body(%{pid: pid, fin: true}) do
+ :ok = Pleroma.Pool.Connections.checkout(pid, self(), :gun_connections)
:done
end