1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Gun.Conn do
7 Struct for gun connection data
10 alias Pleroma.Pool.Connections
14 @type gun_state :: :up | :down
15 @type conn_state :: :active | :idle
17 @type t :: %__MODULE__{
19 gun_state: gun_state(),
20 conn_state: conn_state(),
22 last_reference: pos_integer(),
24 retries: pos_integer()
35 @spec open(String.t() | URI.t(), atom(), keyword()) :: :ok | nil
36 def open(url, name, opts \\ [])
37 def open(url, name, opts) when is_binary(url), do: open(URI.parse(url), name, opts)
39 def open(%URI{} = uri, name, opts) do
40 pool_opts = Pleroma.Config.get([:connections_pool], [])
45 |> Map.put_new(:retry, pool_opts[:retry] || 1)
46 |> Map.put_new(:retry_timeout, pool_opts[:retry_timeout] || 1000)
47 |> Map.put_new(:await_up_timeout, pool_opts[:await_up_timeout] || 5_000)
48 |> maybe_add_tls_opts(uri)
50 key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
52 Logger.debug("opening new connection #{Connections.compose_uri_log(uri)}")
55 if Connections.count(name) < opts[:max_connection] do
58 close_least_used_and_do_open(name, uri, opts)
61 if is_pid(conn_pid) do
62 conn = %Pleroma.Gun.Conn{
66 last_reference: :os.system_time(:second)
69 :ok = Gun.set_owner(conn_pid, Process.whereis(name))
70 Connections.add_conn(name, key, conn)
74 defp maybe_add_tls_opts(opts, %URI{scheme: "http"}), do: opts
76 defp maybe_add_tls_opts(opts, %URI{scheme: "https", host: host}) do
79 cacertfile: CAStore.file_path(),
81 reuse_sessions: false,
83 {&:ssl_verify_hostname.verify_fun/3,
84 [check_hostname: Pleroma.HTTP.Connection.format_host(host)]}
88 if Keyword.keyword?(opts[:tls_opts]) do
89 Keyword.merge(tls_opts, opts[:tls_opts])
94 Map.put(opts, :tls_opts, tls_opts)
97 defp do_open(uri, %{proxy: {proxy_host, proxy_port}} = opts) do
100 |> destination_opts()
101 |> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
103 with open_opts <- Map.delete(opts, :tls_opts),
104 {:ok, conn} <- Gun.open(proxy_host, proxy_port, open_opts),
105 {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]),
106 stream <- Gun.connect(conn, connect_opts),
107 {:response, :fin, 200, _} <- Gun.await(conn, stream) do
112 "Received error on opening connection with http proxy #{
113 Connections.compose_uri_log(uri)
121 defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts) do
133 |> destination_opts()
134 |> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
135 |> Map.put(:version, version)
139 |> Map.put(:protocols, [:socks])
140 |> Map.put(:socks_opts, socks_opts)
142 with {:ok, conn} <- Gun.open(proxy_host, proxy_port, opts),
143 {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do
148 "Received error on opening connection with socks proxy #{
149 Connections.compose_uri_log(uri)
157 defp do_open(%URI{host: host, port: port} = uri, opts) do
158 host = Pleroma.HTTP.Connection.parse_host(host)
160 with {:ok, conn} <- Gun.open(host, port, opts),
161 {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do
166 "Received error on opening connection #{Connections.compose_uri_log(uri)} #{
175 defp destination_opts(%URI{host: host, port: port}) do
176 host = Pleroma.HTTP.Connection.parse_host(host)
177 %{host: host, port: port}
180 defp add_http2_opts(opts, "https", tls_opts) do
181 Map.merge(opts, %{protocols: [:http2], transport: :tls, tls_opts: tls_opts})
184 defp add_http2_opts(opts, _, _), do: opts
186 defp close_least_used_and_do_open(name, uri, opts) do
187 Logger.debug("try to open conn #{Connections.compose_uri_log(uri)}")
189 with [{close_key, least_used} | _conns] <-
190 Connections.get_unused_conns(name),
191 :ok <- Gun.close(least_used.conn) do
192 Connections.remove_conn(name, close_key)
196 [] -> {:error, :pool_overflowed}