summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--config/config.exs70
-rw-r--r--config/description.exs2
-rw-r--r--config/test.exs2
-rw-r--r--docs/API/admin_api.md2
-rw-r--r--docs/configuration/cheatsheet.md38
-rw-r--r--lib/mix/tasks/pleroma/benchmark.ex39
-rw-r--r--lib/mix/tasks/pleroma/emoji.ex9
-rw-r--r--lib/pleroma/application.ex90
-rw-r--r--lib/pleroma/config/config_db.ex11
-rw-r--r--lib/pleroma/config/transfer_task.ex43
-rw-r--r--lib/pleroma/gun/api.ex29
-rw-r--r--lib/pleroma/gun/api/mock.ex154
-rw-r--r--lib/pleroma/gun/conn.ex175
-rw-r--r--lib/pleroma/gun/gun.ex48
-rw-r--r--lib/pleroma/http/adapter.ex64
-rw-r--r--lib/pleroma/http/adapter/gun.ex142
-rw-r--r--lib/pleroma/http/adapter/hackney.ex41
-rw-r--r--lib/pleroma/http/connection.ex119
-rw-r--r--lib/pleroma/http/http.ex154
-rw-r--r--lib/pleroma/http/request.ex23
-rw-r--r--lib/pleroma/http/request_builder.ex105
-rw-r--r--lib/pleroma/object/fetcher.ex6
-rw-r--r--lib/pleroma/otp_version.ex63
-rw-r--r--lib/pleroma/pool/connections.ex322
-rw-r--r--lib/pleroma/pool/pool.ex22
-rw-r--r--lib/pleroma/pool/request.ex72
-rw-r--r--lib/pleroma/pool/supervisor.ex36
-rw-r--r--lib/pleroma/reverse_proxy/client.ex26
-rw-r--r--lib/pleroma/reverse_proxy/client/hackney.ex24
-rw-r--r--lib/pleroma/reverse_proxy/client/tesla.ex87
-rw-r--r--lib/pleroma/reverse_proxy/reverse_proxy.ex20
-rw-r--r--lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex14
-rw-r--r--lib/pleroma/web/rel_me.ex18
-rw-r--r--lib/pleroma/web/rich_media/parser.ex18
-rw-r--r--lib/pleroma/web/web_finger/web_finger.ex2
-rw-r--r--mix.exs10
-rw-r--r--mix.lock50
-rw-r--r--restarter/lib/pleroma.ex4
-rw-r--r--test/activity/ir/topics_test.exs2
-rw-r--r--test/config/config_db_test.exs8
-rw-r--r--test/fixtures/warnings/otp_version/21.11
-rw-r--r--test/fixtures/warnings/otp_version/22.11
-rw-r--r--test/fixtures/warnings/otp_version/22.41
-rw-r--r--test/fixtures/warnings/otp_version/23.01
-rw-r--r--test/fixtures/warnings/otp_version/error1
-rw-r--r--test/fixtures/warnings/otp_version/undefined1
-rw-r--r--test/gun/gun_test.exs39
-rw-r--r--test/http/adapter/gun_test.exs267
-rw-r--r--test/http/adapter/hackney_test.exs54
-rw-r--r--test/http/adapter_test.exs65
-rw-r--r--test/http/connection_test.exs142
-rw-r--r--test/http/request_builder_test.exs30
-rw-r--r--test/http_test.exs35
-rw-r--r--test/notification_test.exs7
-rw-r--r--test/otp_version_test.exs58
-rw-r--r--test/pool/connections_test.exs977
-rw-r--r--test/reverse_proxy/client/tesla_test.exs93
-rw-r--r--test/reverse_proxy/reverse_proxy_test.exs (renamed from test/reverse_proxy_test.exs)121
-rw-r--r--test/support/http_request_mock.ex94
-rw-r--r--test/user_invite_token_test.exs4
-rw-r--r--test/web/admin_api/admin_api_controller_test.exs9
-rw-r--r--test/web/common_api/common_api_utils_test.exs7
-rw-r--r--test/web/push/impl_test.exs2
64 files changed, 3759 insertions, 416 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12f7e8fab..7afe5c21b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -74,6 +74,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- User settings: Add _This account is a_ option.
- A new users admin digest email
- OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`).
+- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
- Add an option `authorized_fetch_mode` to require HTTP signatures for AP fetches.
- ActivityPub: support for `replies` collection (output for outgoing federation & fetching on incoming federation).
- Mix task to refresh counter cache (`mix pleroma.refresh_counter_cache`)
diff --git a/config/config.exs b/config/config.exs
index 0dde1fc85..ed074a99c 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -49,7 +49,8 @@ config :pleroma, ecto_repos: [Pleroma.Repo]
config :pleroma, Pleroma.Repo,
types: Pleroma.PostgresTypes,
telemetry_event: [Pleroma.Repo.Instrumenter],
- migration_lock: nil
+ migration_lock: nil,
+ parameters: [gin_fuzzy_search_limit: "500"]
config :pleroma, Pleroma.Captcha,
enabled: true,
@@ -58,20 +59,6 @@ config :pleroma, Pleroma.Captcha,
config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch"
-config :pleroma, :hackney_pools,
- federation: [
- max_connections: 50,
- timeout: 150_000
- ],
- media: [
- max_connections: 50,
- timeout: 150_000
- ],
- upload: [
- max_connections: 25,
- timeout: 300_000
- ]
-
# Upload configuration
config :pleroma, Pleroma.Upload,
uploader: Pleroma.Uploaders.Local,
@@ -185,20 +172,12 @@ config :mime, :types, %{
}
config :tesla, adapter: Tesla.Adapter.Hackney
-
# Configures http settings, upstream proxy etc.
config :pleroma, :http,
proxy_url: nil,
send_user_agent: true,
user_agent: :default,
- adapter: [
- 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"]
- ]
- ]
+ adapter: []
config :pleroma, :instance,
name: "Pleroma",
@@ -623,7 +602,48 @@ config :pleroma, :modules, runtime_dir: "instance/modules"
config :pleroma, configurable_from_database: false
-config :pleroma, Pleroma.Repo, parameters: [gin_fuzzy_search_limit: "500"]
+config :pleroma, :connections_pool,
+ receive_connection_timeout: 250,
+ max_connections: 250,
+ retry: 0,
+ retry_timeout: 100,
+ await_up_timeout: 5_000
+
+config :pleroma, :pools,
+ federation: [
+ size: 50,
+ max_overflow: 10,
+ timeout: 150_000
+ ],
+ media: [
+ size: 50,
+ max_overflow: 10,
+ timeout: 150_000
+ ],
+ upload: [
+ size: 25,
+ max_overflow: 5,
+ timeout: 300_000
+ ],
+ default: [
+ size: 10,
+ max_overflow: 2,
+ timeout: 10_000
+ ]
+
+config :pleroma, :hackney_pools,
+ federation: [
+ max_connections: 50,
+ timeout: 150_000
+ ],
+ media: [
+ max_connections: 50,
+ timeout: 150_000
+ ],
+ upload: [
+ max_connections: 25,
+ timeout: 300_000
+ ]
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
diff --git a/config/description.exs b/config/description.exs
index aa8a8d790..53edaa6a8 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -2587,7 +2587,7 @@ config :pleroma, :config_description, [
key: :adapter,
type: :module,
description: "Tesla adapter",
- suggestions: [Tesla.Adapter.Hackney]
+ suggestions: [Tesla.Adapter.Hackney, Tesla.Adapter.Gun]
}
]
},
diff --git a/config/test.exs b/config/test.exs
index 6bea09380..d4c641eef 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -94,6 +94,8 @@ config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
config :pleroma, :modules, runtime_dir: "test/fixtures/modules"
+config :pleroma, Pleroma.Gun.API, Pleroma.Gun.API.Mock
+
config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: true
if File.exists?("./config/test.secret.exs") do
diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md
index 91c76ce00..e3f494795 100644
--- a/docs/API/admin_api.md
+++ b/docs/API/admin_api.md
@@ -751,6 +751,8 @@ Some modifications are necessary to save the config settings correctly:
Most of the settings will be applied in `runtime`, this means that you don't need to restart the instance. But some settings are applied in `compile time` and require a reboot of the instance, such as:
- all settings inside these keys:
- `:hackney_pools`
+ - `:connections_pool`
+ - `:pools`
- `:chat`
- partially settings inside these keys:
- `:seconds_valid` in `Pleroma.Captcha`
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index ac55a0b32..d5a978c5a 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -369,8 +369,7 @@ Available caches:
* `proxy_url`: an upstream proxy to fetch posts and/or media with, (default: `nil`)
* `send_user_agent`: should we include a user agent with HTTP requests? (default: `true`)
* `user_agent`: what user agent should we use? (default: `:default`), must be string or `:default`
-* `adapter`: array of hackney options
-
+* `adapter`: array of adapter options
### :hackney_pools
@@ -389,6 +388,41 @@ For each pool, the options are:
* `timeout` - retention duration for connections
+### :connections_pool
+
+*For `gun` adapter*
+
+Advanced settings for connections pool. Pool with opened connections. These connections can be reused in worker pools.
+
+For big instances it's recommended to increase `max_connections` up to 500-1000. It will increase memory usage, but federation would work faster.
+
+* `:receive_connection_timeout` - timeout to receive connection from pool. Default: 250ms.
+* `:max_connections` - maximum number of connections in the pool. Default: 250 connections.
+* `:retry` - number of retries, while `gun` will try to reconnect if connections goes down. Default: 5.
+* `:retry_timeout` - timeout while `gun` will try to reconnect. Default: 100ms.
+* `:await_up_timeout` - timeout while `gun` will wait until connection is up. Default: 5000ms.
+
+### :pools
+
+*For `gun` adapter*
+
+Advanced settings for workers pools.
+
+There's four pools used:
+
+* `:federation` for the federation jobs.
+ You may want this pool max_connections to be at least equal to the number of federator jobs + retry queue jobs.
+* `:media` for rich media, media proxy
+* `:upload` for uploaded media (if using a remote uploader and `proxy_remote: true`)
+* `:default` for other requests
+
+For each pool, the options are:
+
+* `:size` - how much workers the pool can hold
+* `:timeout` - timeout while `gun` will wait for response
+* `:max_overflow` - additional workers if pool is under load
+
+
## Captcha
### Pleroma.Captcha
diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex
index 84dccf7f3..7a7430289 100644
--- a/lib/mix/tasks/pleroma/benchmark.ex
+++ b/lib/mix/tasks/pleroma/benchmark.ex
@@ -74,4 +74,43 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
inputs: inputs
)
end
+
+ def run(["adapters"]) do
+ start_pleroma()
+
+ :ok =
+ Pleroma.Gun.Conn.open(
+ "https://httpbin.org/stream-bytes/1500",
+ :gun_connections
+ )
+
+ Process.sleep(1_500)
+
+ Benchee.run(
+ %{
+ "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]
+ )
+ end,
+ "Without conn and with pool" => fn ->
+ {:ok, %Tesla.Env{}} =
+ Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [],
+ adapter: [receive_conn: false]
+ )
+ end,
+ "With reused conn and without pool" => fn ->
+ {:ok, %Tesla.Env{}} =
+ Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [],
+ adapter: [pool: :no_pool]
+ )
+ end,
+ "With reused conn and with pool" => fn ->
+ {:ok, %Tesla.Env{}} = Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500")
+ end
+ },
+ parallel: 10
+ )
+ end
end
diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex
index 74bf968fc..adac47c86 100644
--- a/lib/mix/tasks/pleroma/emoji.ex
+++ b/lib/mix/tasks/pleroma/emoji.ex
@@ -4,13 +4,13 @@
defmodule Mix.Tasks.Pleroma.Emoji do
use Mix.Task
+ import Mix.Pleroma
@shortdoc "Manages emoji packs"
@moduledoc File.read!("docs/administration/CLI_tasks/emoji.md")
def run(["ls-packs" | args]) do
- Mix.Pleroma.start_pleroma()
- Application.ensure_all_started(:hackney)
+ start_pleroma()
{options, [], []} = parse_global_opts(args)
@@ -36,8 +36,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
end
def run(["get-packs" | args]) do
- Mix.Pleroma.start_pleroma()
- Application.ensure_all_started(:hackney)
+ start_pleroma()
{options, pack_names, []} = parse_global_opts(args)
@@ -135,7 +134,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
end
def run(["gen-pack", src]) do
- Application.ensure_all_started(:hackney)
+ start_pleroma()
proposed_name = Path.basename(src) |> Path.rootname()
name = String.trim(IO.gets("Pack name [#{proposed_name}]: "))
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 27758cf94..df6d3a98d 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -3,8 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Application do
- import Cachex.Spec
use Application
+
+ import Cachex.Spec
+
+ alias Pleroma.Config
+
require Logger
@name Mix.Project.config()[:name]
@@ -18,9 +22,9 @@ defmodule Pleroma.Application do
def repository, do: @repository
def user_agent do
- case Pleroma.Config.get([:http, :user_agent], :default) do
+ case Config.get([:http, :user_agent], :default) do
:default ->
- info = "#{Pleroma.Web.base_url()} <#{Pleroma.Config.get([:instance, :email], "")}>"
+ info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>"
named_version() <> "; " <> info
custom ->
@@ -32,7 +36,7 @@ defmodule Pleroma.Application do
# for more information on OTP Applications
def start(_type, _args) do
Pleroma.HTML.compile_scrubbers()
- Pleroma.Config.DeprecationWarnings.warn()
+ Config.DeprecationWarnings.warn()
Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
Pleroma.Repo.check_migrations_applied!()
setup_instrumenters()
@@ -42,17 +46,17 @@ defmodule Pleroma.Application do
children =
[
Pleroma.Repo,
- Pleroma.Config.TransferTask,
+ Config.TransferTask,
Pleroma.Emoji,
Pleroma.Captcha,
Pleroma.Plugs.RateLimiter.Supervisor
] ++
cachex_children() ++
- hackney_pool_children() ++
+ http_pools_children(Config.get(:env)) ++
[
Pleroma.Stats,
Pleroma.JobQueueMonitor,
- {Oban, Pleroma.Config.get(Oban)}
+ {Oban, Config.get(Oban)}
] ++
task_children(@env) ++
streamer_child(@env) ++
@@ -62,6 +66,18 @@ defmodule Pleroma.Application do
Pleroma.Gopher.Server
]
+ case Pleroma.OTPVersion.check_version() do
+ :ok -> :ok
+ {:error, version} -> raise "
+ !!!OTP VERSION WARNING!!!
+ You are using gun adapter with OTP version #{version}, which doesn't support correct handling of unordered certificates chains.
+ "
+ :undefined -> raise "
+ !!!OTP VERSION WARNING!!!
+ To support correct handling of unordered certificates chains - OTP version must be > 22.2.
+ "
+ end
+
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
@@ -69,7 +85,7 @@ defmodule Pleroma.Application do
end
def load_custom_modules do
- dir = Pleroma.Config.get([:modules, :runtime_dir])
+ dir = Config.get([:modules, :runtime_dir])
if dir && File.exists?(dir) do
dir
@@ -110,20 +126,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()),
@@ -145,7 +147,7 @@ defmodule Pleroma.Application do
do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60))
defp seconds_valid_interval,
- do: :timer.seconds(Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid]))
+ do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid]))
defp build_cachex(type, opts),
do: %{
@@ -154,7 +156,7 @@ defmodule Pleroma.Application do
type: :worker
}
- defp chat_enabled?, do: Pleroma.Config.get([:chat, :enabled])
+ defp chat_enabled?, do: Config.get([:chat, :enabled])
defp streamer_child(:test), do: []
@@ -168,13 +170,6 @@ 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)
- end
- end
-
defp task_children(:test) do
[
%{
@@ -199,4 +194,37 @@ defmodule Pleroma.Application do
}
]
end
+
+ # start hackney and gun pools in tests
+ defp http_pools_children(:test) do
+ hackney_options = Config.get([:hackney_pools, :federation])
+ hackney_pool = :hackney_pool.child_spec(:federation, hackney_options)
+ [hackney_pool, Pleroma.Pool.Supervisor]
+ end
+
+ defp http_pools_children(_) do
+ :tesla
+ |> Application.get_env(:adapter)
+ |> http_pools()
+ end
+
+ defp http_pools(Tesla.Adapter.Hackney) do
+ pools = [:federation, :media]
+
+ pools =
+ if Config.get([Pleroma.Upload, :proxy_remote]) do
+ [:upload | pools]
+ else
+ pools
+ end
+
+ for pool <- pools do
+ options = Config.get([:hackney_pools, pool])
+ :hackney_pool.child_spec(pool, options)
+ end
+ end
+
+ defp http_pools(Tesla.Adapter.Gun), do: [Pleroma.Pool.Supervisor]
+
+ defp http_pools(_), do: []
end
diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex
index 119251bee..bdacefa97 100644
--- a/lib/pleroma/config/config_db.ex
+++ b/lib/pleroma/config/config_db.ex
@@ -278,8 +278,6 @@ defmodule Pleroma.ConfigDB do
}
end
- defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]}
-
defp do_convert(entity) when is_tuple(entity) do
value =
entity
@@ -323,15 +321,6 @@ defmodule Pleroma.ConfigDB do
{:proxy_url, {do_transform_string(type), parse_host(host), port}}
end
- defp do_transform(%{"tuple" => [":partial_chain", entity]}) do
- {partial_chain, []} =
- entity
- |> String.replace(~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
- |> Code.eval_string()
-
- {: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/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
index f037ce8a5..0087ab0e4 100644
--- a/lib/pleroma/config/transfer_task.ex
+++ b/lib/pleroma/config/transfer_task.ex
@@ -18,7 +18,10 @@ defmodule Pleroma.Config.TransferTask do
{:pleroma, Oban},
{:pleroma, :rate_limit},
{:pleroma, :markup},
- {:plerome, :streamer}
+ {:pleroma, :streamer},
+ {:pleroma, :pools},
+ {:pleroma, :connections_pool},
+ {:tesla, :adapter}
]
@reboot_time_subkeys [
@@ -74,6 +77,28 @@ defmodule Pleroma.Config.TransferTask do
end
end
+ defp group_for_restart(:logger, key, _, merged_value) do
+ # change logger configuration in runtime, without restart
+ if Keyword.keyword?(merged_value) and
+ key not in [:compile_time_application, :backends, :compile_time_purge_matching] do
+ Logger.configure_backend(key, merged_value)
+ else
+ Logger.configure([{key, merged_value}])
+ end
+
+ nil
+ end
+
+ defp group_for_restart(:tesla, _, _, _), do: :pleroma
+
+ defp group_for_restart(group, _, _, _) when group != :pleroma, do: group
+
+ defp group_for_restart(group, key, value, _) do
+ if pleroma_need_restart?(group, key, value) do
+ group
+ end
+ end
+
defp merge_and_update(setting) do
try do
key = ConfigDB.from_string(setting.key)
@@ -95,21 +120,7 @@ defmodule Pleroma.Config.TransferTask do
:ok = update_env(group, key, merged_value)
- if group != :logger do
- if group != :pleroma or pleroma_need_restart?(group, key, value) do
- group
- end
- else
- # change logger configuration in runtime, without restart
- if Keyword.keyword?(merged_value) and
- key not in [:compile_time_application, :backends, :compile_time_purge_matching] do
- Logger.configure_backend(key, merged_value)
- else
- Logger.configure([{key, merged_value}])
- end
-
- nil
- end
+ group_for_restart(group, key, value, merged_value)
rescue
error ->
error_msg =
diff --git a/lib/pleroma/gun/api.ex b/lib/pleroma/gun/api.ex
new file mode 100644
index 000000000..f79c9f443
--- /dev/null
+++ b/lib/pleroma/gun/api.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.API do
+ @callback open(charlist(), pos_integer(), map()) :: {:ok, pid()}
+ @callback info(pid()) :: map()
+ @callback close(pid()) :: :ok
+ @callback await_up(pid, pos_integer()) :: {:ok, atom()} | {:error, atom()}
+ @callback connect(pid(), map()) :: reference()
+ @callback await(pid(), reference()) :: {:response, :fin, 200, []}
+ @callback set_owner(pid(), pid()) :: :ok
+
+ 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, timeout \\ 5_000), do: api().await_up(pid, timeout)
+
+ def connect(pid, opts), do: api().connect(pid, opts)
+
+ def await(pid, ref), do: api().await(pid, ref)
+
+ def set_owner(pid, owner), do: api().set_owner(pid, owner)
+
+ defp api, do: Pleroma.Config.get([Pleroma.Gun.API], Pleroma.Gun)
+end
diff --git a/lib/pleroma/gun/api/mock.ex b/lib/pleroma/gun/api/mock.ex
new file mode 100644
index 000000000..6d24b0e69
--- /dev/null
+++ b/lib/pleroma/gun/api/mock.ex
@@ -0,0 +1,154 @@
+# 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('some-domain.com', 443, _) 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
+ })
+
+ {:ok, conn_pid}
+ end
+
+ @impl API
+ def open(ip, port, _)
+ when ip in [{10_755, 10_368, 61_708, 131, 64_206, 45_068, 0, 9_694}, {127, 0, 0, 1}] and
+ port in [80, 443] do
+ {:ok, conn_pid} = Task.start_link(fn -> Process.sleep(1_000) end)
+
+ scheme = if port == 443, do: "https", else: "http"
+
+ Registry.register(API.Mock, conn_pid, %{
+ origin_scheme: scheme,
+ origin_host: ip,
+ origin_port: port
+ })
+
+ {:ok, conn_pid}
+ end
+
+ @impl API
+ def open('localhost', 1234, %{
+ protocols: [:socks],
+ proxy: {:socks5, 'localhost', 1234},
+ socks_opts: %{host: 'proxy-socks.com', port: 80, version: 5}
+ }) do
+ {:ok, conn_pid} = Task.start_link(fn -> Process.sleep(1_000) end)
+
+ Registry.register(API.Mock, conn_pid, %{
+ origin_scheme: "http",
+ origin_host: 'proxy-socks.com',
+ origin_port: 80
+ })
+
+ {:ok, conn_pid}
+ end
+
+ @impl API
+ def open('localhost', 1234, %{
+ protocols: [:socks],
+ proxy: {:socks4, 'localhost', 1234},
+ socks_opts: %{
+ host: 'proxy-socks.com',
+ port: 443,
+ protocols: [:http2],
+ tls_opts: [],
+ transport: :tls,
+ version: 4
+ }
+ }) do
+ {:ok, conn_pid} = Task.start_link(fn -> Process.sleep(1_000) end)
+
+ Registry.register(API.Mock, conn_pid, %{
+ origin_scheme: "https",
+ origin_host: 'proxy-socks.com',
+ origin_port: 443
+ })
+
+ {:ok, conn_pid}
+ end
+
+ @impl API
+ def open('gun-not-up.com', 80, _opts), do: {:error, :timeout}
+
+ @impl API
+ def open('example.com', port, _) when port in [443, 115] do
+ {:ok, conn_pid} = Task.start_link(fn -> Process.sleep(1_000) end)
+
+ Registry.register(API.Mock, conn_pid, %{
+ origin_scheme: "https",
+ origin_host: 'example.com',
+ origin_port: 443
+ })
+
+ {:ok, conn_pid}
+ end
+
+ @impl API
+ def open(domain, 80, _) 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
+ })
+
+ {: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, _timeout), do: {:ok, :http}
+
+ @impl API
+ def set_owner(_pid, _owner), do: :ok
+
+ @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..ddb9f30b0
--- /dev/null
+++ b/lib/pleroma/gun/conn.ex
@@ -0,0 +1,175 @@
+# 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
+ """
+ alias Pleroma.Gun.API
+ alias Pleroma.Pool.Connections
+
+ require Logger
+
+ @type gun_state :: :up | :down
+ @type conn_state :: :active | :idle
+
+ @type t :: %__MODULE__{
+ conn: pid(),
+ gun_state: gun_state(),
+ conn_state: conn_state(),
+ used_by: [pid()],
+ last_reference: pos_integer(),
+ crf: float(),
+ retries: pos_integer()
+ }
+
+ defstruct conn: nil,
+ gun_state: :open,
+ conn_state: :init,
+ used_by: [],
+ last_reference: 0,
+ crf: 1,
+ retries: 0
+
+ @spec open(String.t() | URI.t(), atom(), keyword()) :: :ok | nil
+ def open(url, name, opts \\ [])
+ def open(url, name, opts) when is_binary(url), do: open(URI.parse(url), name, opts)
+
+ def open(%URI{} = uri, name, opts) do
+ pool_opts = Pleroma.Config.get([:connections_pool], [])
+
+ opts =
+ opts
+ |> Enum.into(%{})
+ |> Map.put_new(:retry, pool_opts[:retry] || 0)
+ |> Map.put_new(:retry_timeout, pool_opts[:retry_timeout] || 100)
+ |> Map.put_new(:await_up_timeout, pool_opts[:await_up_timeout] || 5_000)
+
+ key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
+
+ Logger.debug("opening new connection #{Connections.compose_uri_log(uri)}")
+
+ conn_pid =
+ if Connections.count(name) < opts[:max_connection] do
+ do_open(uri, opts)
+ else
+ try_do_open(name, uri, opts)
+ end
+
+ if is_pid(conn_pid) do
+ conn = %Pleroma.Gun.Conn{
+ conn: conn_pid,
+ gun_state: :up,
+ conn_state: :active,
+ last_reference: :os.system_time(:second)
+ }
+
+ :ok = API.set_owner(conn_pid, Process.whereis(name))
+ Connections.add_conn(name, key, conn)
+ end
+ end
+
+ defp do_open(uri, %{proxy: {proxy_host, proxy_port}} = opts) do
+ connect_opts =
+ uri
+ |> destination_opts()
+ |> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
+
+ with open_opts <- Map.delete(opts, :tls_opts),
+ {:ok, conn} <- API.open(proxy_host, proxy_port, open_opts),
+ {:ok, _} <- API.await_up(conn, opts[:await_up_timeout]),
+ stream <- API.connect(conn, connect_opts),
+ {:response, :fin, 200, _} <- API.await(conn, stream) do
+ conn
+ else
+ error ->
+ Logger.warn(
+ "Received error on opening connection with http proxy #{
+ Connections.compose_uri_log(uri)
+ } #{inspect(error)}"
+ )
+
+ nil
+ end
+ end
+
+ defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts) do
+ version =
+ proxy_type
+ |> to_string()
+ |> String.last()
+ |> case do
+ "4" -> 4
+ _ -> 5
+ end
+
+ socks_opts =
+ uri
+ |> destination_opts()
+ |> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
+ |> Map.put(:version, version)
+
+ opts =
+ opts
+ |> Map.put(:protocols, [:socks])
+ |> Map.put(:socks_opts, socks_opts)
+
+ with {:ok, conn} <- API.open(proxy_host, proxy_port, opts),
+ {:ok, _} <- API.await_up(conn, opts[:await_up_timeout]) do
+ conn
+ else
+ error ->
+ Logger.warn(
+ "Received error on opening connection with socks proxy #{
+ Connections.compose_uri_log(uri)
+ } #{inspect(error)}"
+ )
+
+ nil
+ end
+ end
+
+ defp do_open(%URI{host: host, port: port} = uri, opts) do
+ {_type, host} = Pleroma.HTTP.Adapter.domain_or_ip(host)
+
+ with {:ok, conn} <- API.open(host, port, opts),
+ {:ok, _} <- API.await_up(conn, opts[:await_up_timeout]) do
+ conn
+ else
+ error ->
+ Logger.warn(
+ "Received error on opening connection #{Connections.compose_uri_log(uri)} #{
+ inspect(error)
+ }"
+ )
+
+ nil
+ end
+ end
+
+ defp destination_opts(%URI{host: host, port: port}) do
+ {_type, host} = Pleroma.HTTP.Adapter.domain_or_ip(host)
+ %{host: host, port: port}
+ end
+
+ defp add_http2_opts(opts, "https", tls_opts) do
+ Map.merge(opts, %{protocols: [:http2], transport: :tls, tls_opts: tls_opts})
+ end
+
+ defp add_http2_opts(opts, _, _), do: opts
+
+ defp try_do_open(name, uri, opts) do
+ Logger.debug("try to open conn #{Connections.compose_uri_log(uri)}")
+
+ with [{close_key, least_used} | _conns] <-
+ Connections.get_unused_conns(name),
+ :ok <- Pleroma.Gun.API.close(least_used.conn) do
+ Connections.remove_conn(name, close_key)
+
+ do_open(uri, opts)
+ else
+ [] -> nil
+ end
+ end
+end
diff --git a/lib/pleroma/gun/gun.ex b/lib/pleroma/gun/gun.ex
new file mode 100644
index 000000000..da82983b1
--- /dev/null
+++ b/lib/pleroma/gun/gun.ex
@@ -0,0 +1,48 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.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,
+ :socks_opts,
+ :ws_opts
+ ]
+
+ @impl API
+ def open(host, port, opts \\ %{}), do: :gun.open(host, port, Map.take(opts, @gun_keys))
+
+ @impl API
+ defdelegate info(pid), to: :gun
+
+ @impl API
+ defdelegate close(pid), to: :gun
+
+ @impl API
+ defdelegate await_up(pid, timeout \\ 5_000), to: :gun
+
+ @impl API
+ defdelegate connect(pid, opts), to: :gun
+
+ @impl API
+ defdelegate await(pid, ref), to: :gun
+
+ @spec flush(pid() | reference()) :: :ok
+ defdelegate flush(pid), to: :gun
+
+ @impl API
+ defdelegate set_owner(pid, owner), to: :gun
+end
diff --git a/lib/pleroma/http/adapter.ex b/lib/pleroma/http/adapter.ex
new file mode 100644
index 000000000..6166a3eb4
--- /dev/null
+++ b/lib/pleroma/http/adapter.ex
@@ -0,0 +1,64 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.Adapter do
+ alias Pleroma.HTTP.Connection
+
+ @type proxy ::
+ {Connection.host(), pos_integer()}
+ | {Connection.proxy_type(), pos_integer()}
+ @type host_type :: :domain | :ip
+
+ @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
+ with {:ok, host, port} <- Connection.parse_proxy(proxy_url) do
+ {host, port}
+ else
+ {: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)
+
+ @spec domain_or_fallback(String.t()) :: charlist()
+ def domain_or_fallback(host) do
+ case domain_or_ip(host) do
+ {:domain, domain} -> domain
+ {:ip, _ip} -> to_charlist(host)
+ end
+ end
+
+ @spec domain_or_ip(String.t()) :: {host_type(), Connection.host()}
+ def domain_or_ip(host) do
+ charlist = to_charlist(host)
+
+ case :inet.parse_address(charlist) do
+ {:error, :einval} ->
+ {:domain, :idna.encode(charlist)}
+
+ {:ok, ip} when is_tuple(ip) and tuple_size(ip) in [4, 8] ->
+ {:ip, ip}
+ end
+ end
+end
diff --git a/lib/pleroma/http/adapter/gun.ex b/lib/pleroma/http/adapter/gun.ex
new file mode 100644
index 000000000..908d71898
--- /dev/null
+++ b/lib/pleroma/http/adapter/gun.ex
@@ -0,0 +1,142 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.Adapter.Gun do
+ @behaviour Pleroma.HTTP.Adapter
+
+ alias Pleroma.HTTP.Adapter
+
+ require Logger
+
+ alias Pleroma.Pool.Connections
+
+ @defaults [
+ connect_timeout: 5_000,
+ domain_lookup_timeout: 5_000,
+ tls_handshake_timeout: 5_000,
+ retry: 0,
+ await_up_timeout: 5_000
+ ]
+
+ @spec options(keyword(), URI.t()) :: keyword()
+ def options(connection_opts \\ [], %URI{} = uri) do
+ proxy = Pleroma.Config.get([:http, :proxy_url], nil)
+
+ @defaults
+ |> Keyword.merge(Pleroma.Config.get([:http, :adapter], []))
+ |> add_original(uri)
+ |> add_scheme_opts(uri)
+ |> Adapter.maybe_add_proxy(Adapter.format_proxy(proxy))
+ |> maybe_get_conn(uri, connection_opts)
+ end
+
+ @spec after_request(keyword()) :: :ok
+ def after_request(opts) do
+ with conn when not is_nil(conn) <- opts[:conn],
+ body_as when body_as != :chunks <- opts[:body_as] do
+ Connections.checkout(conn, self(), :gun_connections)
+ end
+
+ :ok
+ end
+
+ defp add_original(opts, %URI{host: host, port: port}) do
+ formatted_host = Adapter.domain_or_fallback(host)
+
+ Keyword.put(opts, :original, "#{formatted_host}:#{port}")
+ end
+
+ defp add_scheme_opts(opts, %URI{scheme: "http"}), do: opts
+
+ defp add_scheme_opts(opts, %URI{scheme: "https", host: host, port: port}) do
+ adapter_opts = [
+ certificates_verification: true,
+ tls_opts: [
+ verify: :verify_peer,
+ cacertfile: CAStore.file_path(),
+ depth: 20,
+ reuse_sessions: false,
+ verify_fun:
+ {&:ssl_verify_hostname.verify_fun/3, [check_hostname: Adapter.domain_or_fallback(host)]},
+ log_level: :warning
+ ]
+ ]
+
+ adapter_opts =
+ if port != 443 do
+ Keyword.put(adapter_opts, :transport, :tls)
+ else
+ adapter_opts
+ end
+
+ Keyword.merge(opts, adapter_opts)
+ end
+
+ defp maybe_get_conn(adapter_opts, uri, connection_opts) do
+ {receive_conn?, opts} =
+ adapter_opts
+ |> Keyword.merge(connection_opts)
+ |> Keyword.pop(:receive_conn, true)
+
+ if Connections.alive?(:gun_connections) and receive_conn? do
+ try_to_get_conn(uri, opts)
+ else
+ opts
+ end
+ end
+
+ defp try_to_get_conn(uri, opts) do
+ try do
+ case Connections.checkin(uri, :gun_connections) do
+ nil ->
+ Logger.debug(
+ "Gun connections pool checkin was not successful. Trying to open conn for next request."
+ )
+
+ Task.start(fn -> Pleroma.Gun.Conn.open(uri, :gun_connections, opts) end)
+ opts
+
+ conn when is_pid(conn) ->
+ Logger.debug("received conn #{inspect(conn)} #{Connections.compose_uri_log(uri)}")
+
+ opts
+ |> Keyword.put(:conn, conn)
+ |> Keyword.put(:close_conn, false)
+ end
+ rescue
+ error ->
+ Logger.warn(
+ "Gun connections pool checkin caused error #{Connections.compose_uri_log(uri)} #{
+ inspect(error)
+ }"
+ )
+
+ opts
+ catch
+ # TODO: here must be no timeouts
+ :exit, {:timeout, {_, operation, [_, {method, _}, _]}} ->
+ {:message_queue_len, messages_len} =
+ :gun_connections
+ |> Process.whereis()
+ |> Process.info(:message_queue_len)
+
+ Logger.warn(
+ "Gun connections pool checkin with timeout error for #{operation} #{method} #{
+ Connections.compose_uri_log(uri)
+ }. Messages length: #{messages_len}"
+ )
+
+ opts
+
+ :exit, error ->
+ Logger.warn(
+ "Gun pool checkin exited with error #{Connections.compose_uri_log(uri)} #{
+ inspect(error)
+ }"
+ )
+
+ opts
+ end
+ end
+end
diff --git a/lib/pleroma/http/adapter/hackney.ex b/lib/pleroma/http/adapter/hackney.ex
new file mode 100644
index 000000000..00db30083
--- /dev/null
+++ b/lib/pleroma/http/adapter/hackney.ex
@@ -0,0 +1,41 @@
+defmodule Pleroma.HTTP.Adapter.Hackney do
+ @behaviour Pleroma.HTTP.Adapter
+
+ @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], nil)
+
+ @defaults
+ |> Keyword.merge(Pleroma.Config.get([:http, :adapter], []))
+ |> Keyword.merge(connection_opts)
+ |> add_scheme_opts(uri)
+ |> Pleroma.HTTP.Adapter.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 7e2c6f5e8..e2d7afbbd 100644
--- a/lib/pleroma/http/connection.ex
+++ b/lib/pleroma/http/connection.ex
@@ -4,40 +4,105 @@
defmodule Pleroma.HTTP.Connection do
@moduledoc """
- Connection for http-requests.
+ Configure Tesla.Client with default and customized adapter options.
"""
+ @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()
- @hackney_options [
- connect_timeout: 10_000,
- recv_timeout: 20_000,
- follow_redirect: true,
- force_redirect: true,
- pool: :federation
- ]
- @adapter Application.get_env(:tesla, :adapter)
+ @defaults [pool: :federation]
- @doc """
- Configure a client connection
+ require Logger
- # Returns
+ alias Pleroma.Config
+ alias Pleroma.HTTP.Adapter
- Tesla.Env.client
+ @doc """
+ Merge default connection & adapter options with received ones.
"""
- @spec new(Keyword.t()) :: Tesla.Env.client()
- def new(opts \\ []) do
- Tesla.client([], {@adapter, hackney_options(opts)})
+
+ @spec options(URI.t(), keyword()) :: keyword()
+ def options(%URI{} = uri, opts \\ []) do
+ @defaults
+ |> pool_timeout()
+ |> Keyword.merge(opts)
+ |> adapter().options(uri)
+ end
+
+ defp pool_timeout(opts) do
+ {config_key, default} =
+ if Application.get_env(:tesla, :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().after_request(opts)
+
+ defp adapter do
+ case Application.get_env(:tesla, :adapter) do
+ Tesla.Adapter.Gun -> Adapter.Gun
+ Tesla.Adapter.Hackney -> Adapter.Hackney
+ _ -> Adapter
+ end
end
- # fetch Hackney options
- #
- def hackney_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)
+ @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 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, type, parse_host(host), port}
+ else
+ _ ->
+ Logger.warn("parsing proxy fail #{inspect(proxy)}")
+ {:error, :error_parsing_proxy}
+ end
+ 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)
+
+ 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..ad47dc936 100644
--- a/lib/pleroma/http/http.ex
+++ b/lib/pleroma/http/http.ex
@@ -4,21 +4,47 @@
defmodule Pleroma.HTTP do
@moduledoc """
-
+ Wrapper for `Tesla.request/2`.
"""
alias Pleroma.HTTP.Connection
+ alias Pleroma.HTTP.Request
alias Pleroma.HTTP.RequestBuilder, as: Builder
+ alias Tesla.Client
+ alias Tesla.Env
+
+ require Logger
@type t :: __MODULE__
@doc """
- Builds and perform http request.
+ Performs GET request.
+
+ See `Pleroma.HTTP.request/5`
+ """
+ @spec get(Request.url() | nil, Request.headers(), keyword()) ::
+ nil | {:ok, Env.t()} | {:error, any()}
+ def get(url, headers \\ [], options \\ [])
+ def get(nil, _, _), do: nil
+ def get(url, headers, options), do: request(:get, url, "", headers, options)
+
+ @doc """
+ Performs POST request.
+
+ See `Pleroma.HTTP.request/5`
+ """
+ @spec post(Request.url(), String.t(), Request.headers(), keyword()) ::
+ {:ok, Env.t()} | {:error, any()}
+ def post(url, body, headers \\ [], options \\ []),
+ do: request(:post, url, body, headers, options)
+
+ @doc """
+ Builds and performs http request.
# Arguments:
`method` - :get, :post, :put, :delete
- `url`
- `body`
+ `url` - full url
+ `body` - request body
`headers` - a keyworld list of headers, e.g. `[{"content-type", "text/plain"}]`
`options` - custom, per-request middleware or adapter options
@@ -26,61 +52,97 @@ defmodule Pleroma.HTTP do
`{:ok, %Tesla.Env{}}` or `{:error, error}`
"""
- def request(method, url, body \\ "", headers \\ [], options \\ []) do
+ @spec request(atom(), Request.url(), String.t(), Request.headers(), keyword()) ::
+ {:ok, Env.t()} | {:error, any()}
+ def request(method, url, body, headers, options) when is_binary(url) do
+ with uri <- URI.parse(url),
+ received_adapter_opts <- Keyword.get(options, :adapter, []),
+ adapter_opts <- Connection.options(uri, received_adapter_opts),
+ options <- put_in(options[:adapter], adapter_opts),
+ params <- Keyword.get(options, :params, []),
+ request <- build_request(method, headers, options, url, body, params),
+ client <- Tesla.client([Tesla.Middleware.FollowRedirects], tesla_adapter()),
+ pid <- Process.whereis(adapter_opts[:pool]) do
+ pool_alive? =
+ if tesla_adapter() == Tesla.Adapter.Gun do
+ if pid, do: Process.alive?(pid), else: false
+ 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
+ end
+ end
+
+ @spec request(Client.t(), keyword(), map()) :: {:ok, Env.t()} | {:error, any()}
+ def request(%Client{} = client, request, %{env: :test}), do: request_try(client, request)
+
+ def request(%Client{} = client, request, %{body_as: :chunks}) do
+ request_try(client, request)
+ end
+
+ def request(%Client{} = client, request, %{pool_alive?: false}) do
+ request_try(client, request)
+ end
+
+ def request(%Client{} = client, request, %{pool: pool, timeout: timeout}) do
try do
- options =
- process_request_options(options)
- |> process_sni_options(url)
-
- 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)).()
+ :poolboy.transaction(
+ pool,
+ &Pleroma.Pool.Request.execute(&1, client, request, timeout + 500),
+ timeout + 1_000
+ )
rescue
e ->
{:error, e}
catch
+ :exit, {:timeout, _} ->
+ Logger.warn("Receive response from pool failed #{request[:url]}")
+ {:error, :recv_pool_timeout}
+
:exit, e ->
{:error, e}
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
+ @spec request_try(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()}
+ def request_try(client, request) do
+ try do
+ Tesla.request(client, request)
+ rescue
+ e ->
+ {:error, e}
+ catch
+ :exit, e ->
+ {:error, e}
end
end
- def process_request_options(options) do
- Keyword.merge(Pleroma.HTTP.Connection.hackney_options([]), options)
+ defp build_request(method, headers, options, url, body, params) 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
- @doc """
- Performs GET request.
-
- See `Pleroma.HTTP.request/5`
- """
- def get(url, headers \\ [], options \\ []),
- do: request(:get, url, "", headers, options)
-
- @doc """
- Performs POST request.
-
- See `Pleroma.HTTP.request/5`
- """
- def post(url, body, headers \\ [], options \\ []),
- do: request(:post, url, body, headers, options)
+ defp tesla_adapter, do: Application.get_env(:tesla, :adapter)
end
diff --git a/lib/pleroma/http/request.ex b/lib/pleroma/http/request.ex
new file mode 100644
index 000000000..891d88d53
--- /dev/null
+++ b/lib/pleroma/http/request.ex
@@ -0,0 +1,23 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 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
index e23457999..491acd0f9 100644
--- a/lib/pleroma/http/request_builder.ex
+++ b/lib/pleroma/http/request_builder.ex
@@ -7,77 +7,54 @@ defmodule Pleroma.HTTP.RequestBuilder do
Helper functions for building Tesla requests
"""
- @doc """
- Specify the request method when building a request
-
- ## Parameters
-
- - request (Map) - Collected request options
- - m (atom) - Request method
+ alias Pleroma.HTTP.Request
+ alias Tesla.Multipart
- ## Returns
-
- Map
+ @doc """
+ Creates new request
"""
- @spec method(map(), atom) :: map()
- def method(request, m) do
- Map.put_new(request, :method, m)
- end
+ @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}
- ## Parameters
-
- - request (Map) - Collected request options
- - u (String) - Request URL
-
- ## Returns
-
- Map
+ @doc """
+ Specify the request method when building a request
"""
- @spec url(map(), String.t()) :: map()
- def url(request, u) do
- Map.put_new(request, :url, u)
- end
+ @spec url(Request.t(), Request.url()) :: Request.t()
+ def url(request, u), do: %{request | url: u}
@doc """
Add headers to the request
"""
- @spec headers(map(), list(tuple)) :: map()
- def headers(request, header_list) do
- header_list =
+ @spec headers(Request.t(), Request.headers()) :: Request.t()
+ def headers(request, headers) do
+ headers_list =
if Pleroma.Config.get([:http, :send_user_agent]) do
- header_list ++ [{"User-Agent", Pleroma.Application.user_agent()}]
+ headers ++ [{"user-agent", Pleroma.Application.user_agent()}]
else
- header_list
+ headers
end
- Map.put_new(request, :headers, header_list)
+ %{request | headers: headers_list}
end
@doc """
Add custom, per-request middleware or adapter options to the request
"""
- @spec opts(map(), Keyword.t()) :: map()
- def opts(request, options) do
- Map.put_new(request, :opts, options)
- end
+ @spec opts(Request.t(), keyword()) :: Request.t()
+ def opts(request, options), do: %{request | opts: options}
+ # NOTE: isn't used anywhere
@doc """
Add optional parameters to the request
- ## Parameters
-
- - request (Map) - Collected request options
- - definitions (Map) - Map of parameter name to parameter location.
- - options (KeywordList) - The provided optional parameters
-
- ## Returns
-
- Map
"""
- @spec add_optional_params(map(), %{optional(atom) => atom}, keyword()) :: map()
+ @spec add_optional_params(Request.t(), %{optional(atom) => atom}, keyword()) :: map()
def add_optional_params(request, _, []), do: request
def add_optional_params(request, definitions, [{key, value} | tail]) do
@@ -94,49 +71,43 @@ defmodule Pleroma.HTTP.RequestBuilder do
@doc """
Add optional parameters to the request
-
- ## Parameters
-
- - request (Map) - Collected request options
- - location (atom) - Where to put the parameter
- - key (atom) - The name of the parameter
- - value (any) - The value of the parameter
-
- ## Returns
-
- Map
"""
- @spec add_param(map(), atom, atom, any()) :: map()
- def add_param(request, :query, :query, values), do: Map.put(request, :query, values)
+ @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: Map.put(request, :body, value)
+ def add_param(request, :body, :body, value), do: %{request | body: value}
def add_param(request, :body, key, value) do
request
- |> Map.put_new_lazy(:body, &Tesla.Multipart.new/0)
+ |> Map.put(:body, Multipart.new())
|> Map.update!(
:body,
- &Tesla.Multipart.add_field(
+ &Multipart.add_field(
&1,
key,
Jason.encode!(value),
- headers: [{:"Content-Type", "application/json"}]
+ headers: [{"content-type", "application/json"}]
)
)
end
def add_param(request, :file, name, path) do
request
- |> Map.put_new_lazy(:body, &Tesla.Multipart.new/0)
- |> Map.update!(:body, &Tesla.Multipart.add_file(&1, path, name: name))
+ |> Map.put(:body, Multipart.new())
+ |> Map.update!(:body, &Multipart.add_file(&1, path, name: name))
end
def add_param(request, :form, name, value) do
- request
- |> Map.update(:body, %{name => value}, &Map.put(&1, name, value))
+ 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/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index 23ecd9e15..d7fd35f1d 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -141,7 +141,7 @@ defmodule Pleroma.Object.Fetcher do
date: date
})
- [{:Signature, signature}]
+ [{"signature", signature}]
end
defp sign_fetch(headers, id, date) do
@@ -154,7 +154,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
@@ -166,7 +166,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/otp_version.ex b/lib/pleroma/otp_version.ex
new file mode 100644
index 000000000..0be189304
--- /dev/null
+++ b/lib/pleroma/otp_version.ex
@@ -0,0 +1,63 @@
+defmodule Pleroma.OTPVersion do
+ @type check_status() :: :undefined | {:error, String.t()} | :ok
+
+ require Logger
+
+ @spec check_version() :: check_status()
+ def check_version do
+ # OTP Version https://erlang.org/doc/system_principles/versions.html#otp-version
+ paths = [
+ Path.join(:code.root_dir(), "OTP_VERSION"),
+ Path.join([:code.root_dir(), "releases", :erlang.system_info(:otp_release), "OTP_VERSION"])
+ ]
+
+ :tesla
+ |> Application.get_env(:adapter)
+ |> get_and_check_version(paths)
+ end
+
+ @spec get_and_check_version(module(), [Path.t()]) :: check_status()
+ def get_and_check_version(Tesla.Adapter.Gun, paths) do
+ paths
+ |> check_files()
+ |> check_version()
+ end
+
+ def get_and_check_version(_, _), do: :ok
+
+ defp check_files([]), do: nil
+
+ defp check_files([path | paths]) do
+ if File.exists?(path) do
+ File.read!(path)
+ else
+ check_files(paths)
+ end
+ end
+
+ defp check_version(nil), do: :undefined
+
+ defp check_version(version) do
+ try do
+ version = String.replace(version, ~r/\r|\n|\s/, "")
+
+ formatted =
+ version
+ |> String.split(".")
+ |> Enum.map(&String.to_integer/1)
+ |> Enum.take(2)
+
+ with [major, minor] when length(formatted) == 2 <- formatted,
+ true <- (major == 22 and minor >= 2) or major > 22 do
+ :ok
+ else
+ false -> {:error, version}
+ _ -> :undefined
+ end
+ rescue
+ _ -> :undefined
+ catch
+ _ -> :undefined
+ end
+ end
+end
diff --git a/lib/pleroma/pool/connections.ex b/lib/pleroma/pool/connections.ex
new file mode 100644
index 000000000..a444f822f
--- /dev/null
+++ b/lib/pleroma/pool/connections.ex
@@ -0,0 +1,322 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Pool.Connections do
+ use GenServer
+
+ alias Pleroma.Config
+
+ require Logger
+
+ @type domain :: String.t()
+ @type conn :: Pleroma.Gun.Conn.t()
+
+ @type t :: %__MODULE__{
+ conns: %{domain() => conn()},
+ opts: keyword()
+ }
+
+ defstruct conns: %{}, opts: []
+
+ alias Pleroma.Gun.API
+
+ @spec start_link({atom(), keyword()}) :: {:ok, pid()}
+ 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() | URI.t(), atom()) :: pid() | nil
+ def checkin(url, name)
+ def checkin(url, name) when is_binary(url), do: checkin(URI.parse(url), name)
+
+ def checkin(%URI{} = uri, name) do
+ timeout = Config.get([:connections_pool, :receive_connection_timeout], 250)
+
+ GenServer.call(
+ name,
+ {:checkin, uri},
+ timeout
+ )
+ end
+
+ @spec alive?(atom()) :: boolean()
+ def alive?(name) do
+ pid = Process.whereis(name)
+ if pid, do: Process.alive?(pid), else: false
+ end
+
+ @spec get_state(atom()) :: t()
+ def get_state(name) do
+ GenServer.call(name, :state)
+ end
+
+ @spec count(atom()) :: pos_integer()
+ def count(name) do
+ GenServer.call(name, :count)
+ end
+
+ @spec get_unused_conns(atom()) :: [{domain(), conn()}]
+ def get_unused_conns(name) do
+ GenServer.call(name, :unused_conns)
+ end
+
+ @spec checkout(pid(), pid(), atom()) :: :ok
+ def checkout(conn, pid, name) do
+ GenServer.cast(name, {:checkout, conn, pid})
+ end
+
+ @spec add_conn(atom(), String.t(), Pleroma.Gun.Conn.t()) :: :ok
+ def add_conn(name, key, conn) do
+ GenServer.cast(name, {:add_conn, key, conn})
+ end
+
+ @spec remove_conn(atom(), String.t()) :: :ok
+ def remove_conn(name, key) do
+ GenServer.cast(name, {:remove_conn, key})
+ end
+
+ @impl true
+ def handle_cast({:add_conn, key, conn}, state) do
+ state = put_in(state.conns[key], conn)
+
+ Process.monitor(conn.conn)
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_cast({:checkout, conn_pid, pid}, state) do
+ Logger.debug("checkout #{inspect(conn_pid)}")
+
+ state =
+ with true <- Process.alive?(conn_pid),
+ {key, conn} <- find_conn(state.conns, conn_pid),
+ used_by <- List.keydelete(conn.used_by, pid, 0) do
+ conn_state =
+ if used_by == [] do
+ :idle
+ else
+ conn.conn_state
+ end
+
+ put_in(state.conns[key], %{conn | conn_state: conn_state, used_by: used_by})
+ else
+ false ->
+ Logger.debug("checkout for closed conn #{inspect(conn_pid)}")
+ state
+
+ nil ->
+ Logger.debug("checkout for alive conn #{inspect(conn_pid)}, but is not in state")
+ state
+ end
+
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_cast({:remove_conn, key}, state) do
+ state = put_in(state.conns, Map.delete(state.conns, key))
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_call({:checkin, uri}, from, state) do
+ key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
+ Logger.debug("checkin #{key}")
+
+ case state.conns[key] do
+ %{conn: conn, gun_state: gun_state} = current_conn when gun_state == :up ->
+ Logger.debug("reusing conn #{key}")
+
+ with time <- :os.system_time(:second),
+ 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]
+ }) do
+ {:reply, conn, state}
+ end
+
+ %{gun_state: gun_state} when gun_state == :down ->
+ {:reply, nil, state}
+
+ nil ->
+ {:reply, nil, state}
+ end
+ end
+
+ @impl true
+ def handle_call(:state, _from, state), do: {:reply, state, state}
+
+ @impl true
+ def handle_call(:count, _from, state) do
+ {:reply, Enum.count(state.conns), state}
+ end
+
+ @impl true
+ def handle_call(:unused_conns, _from, state) do
+ unused_conns =
+ state.conns
+ |> Enum.filter(fn {_k, v} ->
+ v.conn_state == :idle 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)
+
+ {:reply, unused_conns, state}
+ end
+
+ @impl true
+ def handle_info({:gun_up, conn_pid, _protocol}, state) do
+ state =
+ with conn_key when is_binary(conn_key) <- compose_key_gun_info(conn_pid),
+ {key, conn} <- find_conn(state.conns, conn_pid, conn_key),
+ {true, key} <- {Process.alive?(conn_pid), key},
+ time <- :os.system_time(:second),
+ last_reference <- time - conn.last_reference,
+ current_crf <- crf(last_reference, 100, conn.crf) do
+ put_in(state.conns[key], %{
+ conn
+ | gun_state: :up,
+ last_reference: time,
+ crf: current_crf,
+ conn_state: :active,
+ retries: 0
+ })
+ else
+ :error_gun_info ->
+ Logger.debug(":gun.info caused error")
+ state
+
+ {false, key} ->
+ Logger.debug(":gun_up message for closed conn #{inspect(conn_pid)}")
+
+ put_in(
+ state.conns,
+ Map.delete(state.conns, key)
+ )
+
+ nil ->
+ Logger.debug(":gun_up message for conn which is not found in state")
+
+ :ok = API.close(conn_pid)
+
+ state
+ end
+
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info({:gun_down, conn_pid, _protocol, _reason, _killed}, state) do
+ retries = Config.get([:connections_pool, :retry], 0)
+ # we can't get info on this pid, because pid is dead
+ state =
+ with {key, conn} <- find_conn(state.conns, conn_pid),
+ {true, key} <- {Process.alive?(conn_pid), key} do
+ if conn.retries == retries do
+ Logger.debug("closing conn if retries is eq #{inspect(conn_pid)}")
+ :ok = API.close(conn.conn)
+
+ put_in(
+ state.conns,
+ Map.delete(state.conns, key)
+ )
+ else
+ put_in(state.conns[key], %{
+ conn
+ | gun_state: :down,
+ retries: conn.retries + 1
+ })
+ end
+ else
+ {false, key} ->
+ # gun can send gun_down for closed conn, maybe connection is not closed yet
+ Logger.debug(":gun_down message for closed conn #{inspect(conn_pid)}")
+
+ put_in(
+ state.conns,
+ Map.delete(state.conns, key)
+ )
+
+ nil ->
+ Logger.debug(":gun_down message for conn which is not found in state")
+
+ :ok = API.close(conn_pid)
+
+ state
+ end
+
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info({:DOWN, _ref, :process, conn_pid, reason}, state) do
+ Logger.debug("received DOWM message for #{inspect(conn_pid)} reason -> #{inspect(reason)}")
+
+ state =
+ with {key, conn} <- find_conn(state.conns, conn_pid) do
+ Enum.each(conn.used_by, fn {pid, _ref} ->
+ Process.exit(pid, reason)
+ end)
+
+ put_in(
+ state.conns,
+ Map.delete(state.conns, key)
+ )
+ else
+ nil ->
+ Logger.debug(":DOWN message for conn which is not found in state")
+
+ state
+ end
+
+ {:noreply, state}
+ end
+
+ defp compose_key_gun_info(pid) do
+ try do
+ # sometimes :gun.info can raise MatchError, which lead to pool terminate
+ %{origin_host: origin_host, origin_scheme: scheme, origin_port: port} = API.info(pid)
+
+ host =
+ case :inet.ntoa(origin_host) do
+ {:error, :einval} -> origin_host
+ ip -> ip
+ end
+
+ "#{scheme}:#{host}:#{port}"
+ rescue
+ _ -> :error_gun_info
+ end
+ 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
+
+ def crf(current, steps, crf) do
+ 1 + :math.pow(0.5, current / steps) * crf
+ end
+
+ def compose_uri_log(%URI{scheme: scheme, host: host, path: path}) do
+ "#{scheme}://#{host}#{path}"
+ end
+end
diff --git a/lib/pleroma/pool/pool.ex b/lib/pleroma/pool/pool.ex
new file mode 100644
index 000000000..a7ae64ce4
--- /dev/null
+++ b/lib/pleroma/pool/pool.ex
@@ -0,0 +1,22 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Pool do
+ def child_spec(opts) do
+ poolboy_opts =
+ opts
+ |> Keyword.put(:worker_module, Pleroma.Pool.Request)
+ |> Keyword.put(:name, {:local, opts[:name]})
+ |> Keyword.put(:size, opts[:size])
+ |> Keyword.put(:max_overflow, opts[:max_overflow])
+
+ %{
+ id: opts[:id] || {__MODULE__, make_ref()},
+ start: {:poolboy, :start_link, [poolboy_opts, [name: opts[:name]]]},
+ restart: :permanent,
+ shutdown: 5000,
+ type: :worker
+ }
+ end
+end
diff --git a/lib/pleroma/pool/request.ex b/lib/pleroma/pool/request.ex
new file mode 100644
index 000000000..2c3574561
--- /dev/null
+++ b/lib/pleroma/pool/request.ex
@@ -0,0 +1,72 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Pool.Request do
+ use GenServer
+
+ require Logger
+
+ def start_link(args) do
+ GenServer.start_link(__MODULE__, args)
+ end
+
+ @impl true
+ def init(_), do: {:ok, []}
+
+ @spec execute(pid() | atom(), Tesla.Client.t(), keyword(), pos_integer()) ::
+ {:ok, Tesla.Env.t()} | {:error, any()}
+ def execute(pid, client, request, timeout) do
+ GenServer.call(pid, {:execute, client, request}, timeout)
+ end
+
+ @impl true
+ def handle_call({:execute, client, request}, _from, state) do
+ response = Pleroma.HTTP.request_try(client, request)
+
+ {:reply, response, state}
+ end
+
+ @impl true
+ def handle_info({:gun_data, _conn, stream, _, _}, state) do
+ # in some cases if we reuse conn and got {:error, :body_too_large}
+ # gun continues to send messages to this process,
+ # so we flush messages for this request
+ :ok = :gun.flush(stream)
+
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info({:gun_up, _conn, _protocol}, state) do
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info({:gun_down, _conn, _protocol, _reason, _killed}, state) do
+ # don't flush messages here, because gun can reconnect
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info({:gun_error, _conn, stream, _error}, state) do
+ :ok = :gun.flush(stream)
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info({:gun_push, _conn, _stream, _new_stream, _method, _uri, _headers}, state) do
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info({:gun_response, _conn, _stream, _, _status, _headers}, state) do
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info(msg, state) do
+ Logger.warn("Received unexpected message #{inspect(__MODULE__)} #{inspect(msg)}")
+ {:noreply, state}
+ end
+end
diff --git a/lib/pleroma/pool/supervisor.ex b/lib/pleroma/pool/supervisor.ex
new file mode 100644
index 000000000..32be2264d
--- /dev/null
+++ b/lib/pleroma/pool/supervisor.ex
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Pool.Supervisor do
+ use Supervisor
+
+ alias Pleroma.Pool
+
+ def start_link(args) do
+ Supervisor.start_link(__MODULE__, args, name: __MODULE__)
+ end
+
+ def init(_) do
+ children =
+ [
+ %{
+ id: Pool.Connections,
+ start:
+ {Pool.Connections, :start_link,
+ [{:gun_connections, Pleroma.Config.get([:connections_pool])}]}
+ }
+ ] ++ pools()
+
+ Supervisor.init(children, strategy: :one_for_one)
+ end
+
+ defp pools do
+ for {pool_name, pool_opts} <- Pleroma.Config.get([:pools]) do
+ pool_opts
+ |> Keyword.put(:id, {Pool, pool_name})
+ |> Keyword.put(:name, pool_name)
+ |> Pool.child_spec()
+ end
+ end
+end
diff --git a/lib/pleroma/reverse_proxy/client.ex b/lib/pleroma/reverse_proxy/client.ex
index 776c4794c..63261b94c 100644
--- a/lib/pleroma/reverse_proxy/client.ex
+++ b/lib/pleroma/reverse_proxy/client.ex
@@ -3,19 +3,23 @@
# 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()}
- @callback stream_body(reference() | pid() | map()) ::
- {:ok, binary()} | :done | {:error, String.t()}
+ @callback stream_body(map()) :: {:ok, binary(), map()} | :done | {:error, atom() | String.t()}
@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 +27,12 @@ defmodule Pleroma.ReverseProxy.Client do
def close(ref), do: client().close(ref)
defp client do
- Pleroma.Config.get([Pleroma.ReverseProxy.Client], :hackney)
+ :tesla
+ |> Application.get_env(:adapter)
+ |> client()
end
+
+ defp client(Tesla.Adapter.Hackney), do: Pleroma.ReverseProxy.Client.Hackney
+ defp client(Tesla.Adapter.Gun), do: Pleroma.ReverseProxy.Client.Tesla
+ defp client(_), do: Pleroma.Config.get!(Pleroma.ReverseProxy.Client)
end
diff --git a/lib/pleroma/reverse_proxy/client/hackney.ex b/lib/pleroma/reverse_proxy/client/hackney.ex
new file mode 100644
index 000000000..e41560ab0
--- /dev/null
+++ b/lib/pleroma/reverse_proxy/client/hackney.ex
@@ -0,0 +1,24 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ReverseProxy.Client.Hackney do
+ @behaviour Pleroma.ReverseProxy.Client
+
+ @impl true
+ def request(method, url, headers, body, opts \\ []) do
+ :hackney.request(method, url, headers, body, opts)
+ end
+
+ @impl true
+ def stream_body(ref) do
+ case :hackney.stream_body(ref) do
+ :done -> :done
+ {:ok, data} -> {:ok, data, ref}
+ {:error, error} -> {:error, error}
+ end
+ end
+
+ @impl true
+ def close(ref), do: :hackney.close(ref)
+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..55a11b4a8
--- /dev/null
+++ b/lib/pleroma/reverse_proxy/client/tesla.ex
@@ -0,0 +1,87 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ReverseProxy.Client.Tesla do
+ @type headers() :: [{String.t(), String.t()}]
+ @type status() :: pos_integer()
+
+ @behaviour Pleroma.ReverseProxy.Client
+
+ @spec request(atom(), String.t(), headers(), String.t(), keyword()) ::
+ {:ok, status(), headers}
+ | {:ok, status(), headers, map()}
+ | {:error, atom() | String.t()}
+ | no_return()
+
+ @impl true
+ def request(method, url, headers, body, opts \\ []) do
+ _adapter = check_adapter()
+
+ with opts <- Keyword.merge(opts, body_as: :chunks, mode: :passive),
+ {:ok, response} <-
+ Pleroma.HTTP.request(
+ method,
+ url,
+ body,
+ headers,
+ Keyword.put(opts, :adapter, opts)
+ ) do
+ if is_map(response.body) and method != :head do
+ {:ok, response.status, response.headers, response.body}
+ else
+ {:ok, response.status, response.headers}
+ end
+ else
+ {:error, error} -> {:error, error}
+ end
+ end
+
+ @impl true
+ @spec stream_body(map()) :: {:ok, binary(), map()} | {:error, atom() | String.t()} | :done
+ def stream_body(%{pid: pid, opts: opts, fin: true}) do
+ # if connection was sended and there were redirects, we need to close new conn - pid 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)
+
+ :done
+ end
+
+ 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 = check_adapter()
+ adapter.read_chunk(pid, stream, opts)
+ end
+
+ @impl true
+ @spec close(map) :: :ok | no_return()
+ def close(%{pid: pid}) do
+ adapter = check_adapter()
+ adapter.close(pid)
+ end
+
+ defp check_adapter do
+ adapter = Application.get_env(:tesla, :adapter)
+
+ unless adapter == Tesla.Adapter.Gun do
+ raise "#{adapter} doesn't support reading body in chunks"
+ end
+
+ adapter
+ end
+end
diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex
index 2ed719315..9f5710c92 100644
--- a/lib/pleroma/reverse_proxy/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex
@@ -3,8 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReverseProxy do
- alias Pleroma.HTTP
-
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
~w(if-unmodified-since if-none-match if-range range)
@resp_cache_headers ~w(etag date last-modified cache-control)
@@ -61,10 +59,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",
@@ -97,11 +95,7 @@ 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)
- |> Keyword.merge(Keyword.get(opts, :http, []))
- |> HTTP.process_request_options()
+ client_opts = Keyword.merge(@default_options, Keyword.get(opts, :http, []))
req_headers = build_req_headers(conn.req_headers, opts)
@@ -113,7 +107,7 @@ defmodule Pleroma.ReverseProxy do
end
with {:ok, nil} <- Cachex.get(:failed_proxy_url_cache, url),
- {:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts),
+ {:ok, code, headers, client} <- request(method, url, req_headers, client_opts),
:ok <-
header_length_constraint(
headers,
@@ -159,11 +153,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}
@@ -213,7 +207,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/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
index df774b0f7..ade87daf2 100644
--- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
@@ -12,17 +12,23 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
require Logger
- @hackney_options [
- pool: :media,
- recv_timeout: 10_000
+ @options [
+ pool: :media
]
def perform(:prefetch, url) do
Logger.debug("Prefetching #{inspect(url)}")
+ opts =
+ if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
+ Keyword.put(@options, :recv_timeout, 10_000)
+ else
+ @options
+ end
+
url
|> MediaProxy.url()
- |> HTTP.get([], adapter: @hackney_options)
+ |> HTTP.get([], adapter: opts)
end
def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do
diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex
index 540fa65df..b33dd64dd 100644
--- a/lib/pleroma/web/rel_me.ex
+++ b/lib/pleroma/web/rel_me.ex
@@ -3,11 +3,9 @@
# 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,
- with_body: true
+ max_body: 2_000_000
]
if Pleroma.Config.get(:env) == :test do
@@ -25,8 +23,18 @@ defmodule Pleroma.Web.RelMe do
def parse(_), do: {:error, "No URL provided"}
defp parse_url(url) do
+ opts =
+ if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
+ Keyword.merge(@options,
+ recv_timeout: 2_000,
+ with_body: true
+ )
+ else
+ @options
+ end
+
with {:ok, %Tesla.Env{body: html, status: status}} when status in 200..299 <-
- Pleroma.HTTP.get(url, [], adapter: @hackney_options),
+ Pleroma.HTTP.get(url, [], adapter: opts),
{:ok, html_tree} <- Floki.parse_document(html),
data <-
Floki.attribute(html_tree, "link[rel~=me]", "href") ++
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index 9702e90f1..e05a0a204 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -3,11 +3,9 @@
# 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,
- with_body: true
+ max_body: 2_000_000
]
defp parsers do
@@ -77,8 +75,18 @@ defmodule Pleroma.Web.RichMedia.Parser do
end
defp parse_url(url) do
+ opts =
+ if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
+ Keyword.merge(@options,
+ recv_timeout: 2_000,
+ with_body: true
+ )
+ else
+ @options
+ end
+
try do
- {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
+ {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: opts)
html
|> parse_html()
diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex
index b4cc80179..91e9e2271 100644
--- a/lib/pleroma/web/web_finger/web_finger.ex
+++ b/lib/pleroma/web/web_finger/web_finger.ex
@@ -205,7 +205,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)
diff --git a/mix.exs b/mix.exs
index 24f183472..18e33b214 100644
--- a/mix.exs
+++ b/mix.exs
@@ -119,7 +119,15 @@ defmodule Pleroma.Mixfile do
{:calendar, "~> 0.17.4"},
{:cachex, "~> 3.0.2"},
{:poison, "~> 3.0", override: true},
- {:tesla, "~> 1.3", override: true},
+ # {:tesla, "~> 1.3", override: true},
+ {:tesla,
+ github: "alex-strizhakov/tesla",
+ ref: "922cc3db13b421763edbea76246b8ea61c38c6fa",
+ override: true},
+ {:castore, "~> 0.1"},
+ {:cowlib, "~> 2.8", override: true},
+ {:gun,
+ github: "ninenines/gun", ref: "bd6425ab87428cf4c95f4d23e0a48fd065fbd714", override: true},
{:jason, "~> 1.0"},
{:mogrify, "~> 0.6.1"},
{:ex_aws, "~> 2.1"},
diff --git a/mix.lock b/mix.lock
index 5a667055f..10b2fe30d 100644
--- a/mix.lock
+++ b/mix.lock
@@ -9,6 +9,7 @@
"cachex": {:hex, :cachex, "3.0.3", "4e2d3e05814a5738f5ff3903151d5c25636d72a3527251b753f501ad9c657967", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "3aadb1e605747122f60aa7b0b121cca23c14868558157563b3f3e19ea929f7d0"},
"calendar": {:hex, :calendar, "0.17.6", "ec291cb2e4ba499c2e8c0ef5f4ace974e2f9d02ae9e807e711a9b0c7850b9aee", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "738d0e17a93c2ccfe4ddc707bdc8e672e9074c8569498483feb1c4530fb91b2b"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]},
+ "castore": {:hex, :castore, "0.1.5", "591c763a637af2cc468a72f006878584bc6c306f8d111ef8ba1d4c10e0684010", [:mix], [], "hexpm", "6db356b2bc6cc22561e051ff545c20ad064af57647e436650aa24d7d06cd941a"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm", "d8700a0ca4dbb616c22c9b3f6dd539d88deaafec3efe66869d6370c9a559b3e9"},
@@ -20,41 +21,42 @@
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
- "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
+ "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"},
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
- "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm"},
- "ecto": {:hex, :ecto, "3.3.3", "0830bf3aebcbf3d8c1a1811cd581773b6866886c012f52c0f027031fa96a0b53", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
+ "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
+ "ecto": {:hex, :ecto, "3.3.3", "0830bf3aebcbf3d8c1a1811cd581773b6866886c012f52c0f027031fa96a0b53", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "12e368e3c2a2938d7776defaabdae40e82900fc4d8d66120ec1e01dfd8b93c3a"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
- "ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
- "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm"},
+ "ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"},
+ "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"},
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
"ex_aws": {:hex, :ex_aws, "2.1.1", "1e4de2106cfbf4e837de41be41cd15813eabc722315e388f0d6bb3732cec47cd", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "06b6fde12b33bb6d65d5d3493e903ba5a56d57a72350c15285a4298338089e10"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"},
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
- "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
+ "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"},
"ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "b84f6af156264530b312a8ab98ac6088f6b77ae5fe2058305c81434aa01fbaf9"},
- "ex_syslogger": {:hex, :ex_syslogger, "1.5.0", "bc936ee3fd13d9e592cb4c3a1e8a55fccd33b05e3aa7b185f211f3ed263ff8f0", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.0.5", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm"},
- "excoveralls": {:hex, :excoveralls, "0.12.2", "a513defac45c59e310ac42fcf2b8ae96f1f85746410f30b1ff2b710a4b6cd44b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
+ "ex_syslogger": {:hex, :ex_syslogger, "1.5.0", "bc936ee3fd13d9e592cb4c3a1e8a55fccd33b05e3aa7b185f211f3ed263ff8f0", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.0.5", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "f3b4b184dcdd5f356b7c26c6cd72ab0918ba9dfb4061ccfaf519e562942af87b"},
+ "excoveralls": {:hex, :excoveralls, "0.12.2", "a513defac45c59e310ac42fcf2b8ae96f1f85746410f30b1ff2b710a4b6cd44b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "151c476331d49b45601ffc45f43cb3a8beb396b02a34e3777fea0ad34ae57d89"},
"fast_html": {:hex, :fast_html, "1.0.3", "2cc0d4b68496266a1530e0c852cafeaede0bd10cfdee26fda50dc696c203162f", [:make, :mix], [], "hexpm", "ab3d782b639d3c4655fbaec0f9d032c91f8cab8dd791ac7469c2381bc7c32f85"},
"fast_sanitize": {:hex, :fast_sanitize, "0.1.7", "2a7cd8734c88a2de6de55022104f8a3b87f1fdbe8bbf131d9049764b53d50d0d", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f39fe8ea08fbac17487c30bf09b7d9f3e12472e51fb07a88ffeb8fd17da8ab67"},
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
- "floki": {:hex, :floki, "0.25.0", "b1c9ddf5f32a3a90b43b76f3386ca054325dc2478af020e87b5111c19f2284ac", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm"},
+ "floki": {:hex, :floki, "0.25.0", "b1c9ddf5f32a3a90b43b76f3386ca054325dc2478af020e87b5111c19f2284ac", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "631f4e627c46d5ecd347df5a2accdaf0621c77c3693c5b75a8ad58e84c61f242"},
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
"gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"},
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
- "gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm"},
+ "gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"},
+ "gun": {:git, "https://github.com/ninenines/gun.git", "bd6425ab87428cf4c95f4d23e0a48fd065fbd714", [ref: "bd6425ab87428cf4c95f4d23e0a48fd065fbd714"]},
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"},
- "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm"},
+ "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
"http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]},
- "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
+ "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"},
- "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm"},
- "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm"},
+ "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},
+ "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
"libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
@@ -67,38 +69,38 @@
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm", "3bc928d817974fa10cc11e6c89b9a9361e37e96dbbf3d868c41094ec05745dcd"},
"mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm", "052346cf322311c49a0f22789f3698eea030eec09b8c47367f0686ef2634ae14"},
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
- "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm"},
+ "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
"oban": {:hex, :oban, "0.12.1", "695e9490c6e0edfca616d80639528e448bd29b3bff7b7dd10a56c79b00a5d7fb", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1d58d69b8b5a86e7167abbb8cc92764a66f25f12f6172052595067fc6a30a17"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"},
- "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
- "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
- "phoenix_html": {:hex, :phoenix_html, "2.14.0", "d8c6bc28acc8e65f8ea0080ee05aa13d912c8758699283b8d3427b655aabe284", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
+ "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"},
+ "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
+ "phoenix_html": {:hex, :phoenix_html, "2.14.0", "d8c6bc28acc8e65f8ea0080ee05aa13d912c8758699283b8d3427b655aabe284", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0bb30eda478a06dbfbe96728061a93833db3861a49ccb516f839ecb08493fbb"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "ebf1bfa7b3c1c850c04929afe02e2e0d7ab135e0706332c865de03e761676b1f"},
"plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "9902eda2c52ada2a096434682e99a2493f5d06a94d6ac6bcfff9805f952350f1"},
- "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
- "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm"},
+ "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"},
+ "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"},
- "prometheus": {:hex, :prometheus, "4.5.0", "8f4a2246fe0beb50af0f77c5e0a5bb78fe575c34a9655d7f8bc743aad1c6bf76", [:mix, :rebar3], [], "hexpm"},
+ "prometheus": {:hex, :prometheus, "4.5.0", "8f4a2246fe0beb50af0f77c5e0a5bb78fe575c34a9655d7f8bc743aad1c6bf76", [:mix, :rebar3], [], "hexpm", "679b5215480fff612b8351f45c839d995a07ce403e42ff02f1c6b20960d41a4e"},
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"},
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"},
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"},
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"},
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "d736bfa7444112eb840027bb887832a0e403a4a3437f48028c3b29a2dbbd2543"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
- "recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm"},
+ "recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm", "72f3840fedd94f06315c523f6cecf5b4827233bed7ae3fe135b2a0ebeab5e196"},
"remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "825dc00aaba5a1b7c4202a532b696b595dd3bcb3", [ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"]},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
"swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "e3928e1d2889a308aaf3e42755809ac21cffd77cb58eef01cbfdab4ce2fd1e21"},
- "syslog": {:hex, :syslog, "1.0.6", "995970c9aa7feb380ac493302138e308d6e04fd57da95b439a6df5bb3bf75076", [:rebar3], [], "hexpm"},
+ "syslog": {:hex, :syslog, "1.0.6", "995970c9aa7feb380ac493302138e308d6e04fd57da95b439a6df5bb3bf75076", [:rebar3], [], "hexpm", "769ddfabd0d2a16f3f9c17eb7509951e0ca4f68363fb26f2ee51a8ec4a49881a"},
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
- "tesla": {:hex, :tesla, "1.3.2", "deb92c5c9ce35e747a395ba413ca78593a4f75bf0e1545630ee2e3d34264021e", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
+ "tesla": {:git, "https://github.com/alex-strizhakov/tesla.git", "922cc3db13b421763edbea76246b8ea61c38c6fa", [ref: "922cc3db13b421763edbea76246b8ea61c38c6fa"]},
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
"tzdata": {:hex, :tzdata, "0.5.22", "f2ba9105117ee0360eae2eca389783ef7db36d533899b2e84559404dbc77ebb8", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cd66c8a1e6a9e121d1f538b01bef459334bb4029a1ffb4eeeb5e4eae0337e7b6"},
diff --git a/restarter/lib/pleroma.ex b/restarter/lib/pleroma.ex
index d7817909d..4ade890f9 100644
--- a/restarter/lib/pleroma.ex
+++ b/restarter/lib/pleroma.ex
@@ -44,7 +44,7 @@ defmodule Restarter.Pleroma do
end
def handle_cast({:restart, :test, _}, state) do
- Logger.warn("pleroma restarted")
+ Logger.warn("pleroma manually restarted")
{:noreply, Map.put(state, :need_reboot?, false)}
end
@@ -57,7 +57,7 @@ defmodule Restarter.Pleroma do
def handle_cast({:after_boot, _}, %{after_boot: true} = state), do: {:noreply, state}
def handle_cast({:after_boot, :test}, state) do
- Logger.warn("pleroma restarted")
+ Logger.warn("pleroma restarted after boot")
{:noreply, Map.put(state, :after_boot, true)}
end
diff --git a/test/activity/ir/topics_test.exs b/test/activity/ir/topics_test.exs
index e75f83586..8729e5746 100644
--- a/test/activity/ir/topics_test.exs
+++ b/test/activity/ir/topics_test.exs
@@ -83,7 +83,7 @@ defmodule Pleroma.Activity.Ir.TopicsTest do
assert Enum.member?(topics, "hashtag:bar")
end
- test "only converts strinngs to hash tags", %{
+ test "only converts strings to hash tags", %{
activity: %{object: %{data: data} = object} = activity
} do
tagged_data = Map.put(data, "tag", [2])
diff --git a/test/config/config_db_test.exs b/test/config/config_db_test.exs
index 812709fd8..394040a59 100644
--- a/test/config/config_db_test.exs
+++ b/test/config/config_db_test.exs
@@ -478,14 +478,6 @@ defmodule Pleroma.ConfigDBTest do
assert ConfigDB.from_binary(binary) == [key: "value"]
end
- test "keyword with partial_chain key" do
- binary =
- ConfigDB.transform([%{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}])
-
- assert binary == :erlang.term_to_binary(partial_chain: &:hackney_connect.partial_chain/1)
- assert ConfigDB.from_binary(binary) == [partial_chain: &:hackney_connect.partial_chain/1]
- end
-
test "keyword" do
binary =
ConfigDB.transform([
diff --git a/test/fixtures/warnings/otp_version/21.1 b/test/fixtures/warnings/otp_version/21.1
new file mode 100644
index 000000000..90cd64c4f
--- /dev/null
+++ b/test/fixtures/warnings/otp_version/21.1
@@ -0,0 +1 @@
+21.1 \ No newline at end of file
diff --git a/test/fixtures/warnings/otp_version/22.1 b/test/fixtures/warnings/otp_version/22.1
new file mode 100644
index 000000000..d9b314368
--- /dev/null
+++ b/test/fixtures/warnings/otp_version/22.1
@@ -0,0 +1 @@
+22.1 \ No newline at end of file
diff --git a/test/fixtures/warnings/otp_version/22.4 b/test/fixtures/warnings/otp_version/22.4
new file mode 100644
index 000000000..1da8ccd28
--- /dev/null
+++ b/test/fixtures/warnings/otp_version/22.4
@@ -0,0 +1 @@
+22.4 \ No newline at end of file
diff --git a/test/fixtures/warnings/otp_version/23.0 b/test/fixtures/warnings/otp_version/23.0
new file mode 100644
index 000000000..4266d8634
--- /dev/null
+++ b/test/fixtures/warnings/otp_version/23.0
@@ -0,0 +1 @@
+23.0 \ No newline at end of file
diff --git a/test/fixtures/warnings/otp_version/error b/test/fixtures/warnings/otp_version/error
new file mode 100644
index 000000000..8fdd954df
--- /dev/null
+++ b/test/fixtures/warnings/otp_version/error
@@ -0,0 +1 @@
+22 \ No newline at end of file
diff --git a/test/fixtures/warnings/otp_version/undefined b/test/fixtures/warnings/otp_version/undefined
new file mode 100644
index 000000000..66dc9051d
--- /dev/null
+++ b/test/fixtures/warnings/otp_version/undefined
@@ -0,0 +1 @@
+undefined \ No newline at end of file
diff --git a/test/gun/gun_test.exs b/test/gun/gun_test.exs
new file mode 100644
index 000000000..9f3e0f938
--- /dev/null
+++ b/test/gun/gun_test.exs
@@ -0,0 +1,39 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.GunTest do
+ use ExUnit.Case
+ alias Pleroma.Gun
+
+ @moduletag :integration
+
+ test "opens connection and receive response" do
+ {:ok, conn} = Gun.open('httpbin.org', 443)
+ assert is_pid(conn)
+ {:ok, _protocol} = Gun.await_up(conn)
+ ref = :gun.get(conn, '/get?a=b&c=d')
+ assert is_reference(ref)
+
+ assert {:response, :nofin, 200, _} = Gun.await(conn, ref)
+ assert json = receive_response(conn, ref)
+
+ assert %{"args" => %{"a" => "b", "c" => "d"}} = Jason.decode!(json)
+
+ {:ok, pid} = Task.start(fn -> Process.sleep(50) end)
+
+ :ok = :gun.set_owner(conn, pid)
+
+ assert :gun.info(conn).owner == pid
+ end
+
+ defp receive_response(conn, ref, acc \\ "") do
+ case Gun.await(conn, ref) do
+ {:data, :nofin, body} ->
+ receive_response(conn, ref, acc <> body)
+
+ {:data, :fin, body} ->
+ acc <> body
+ end
+ end
+end
diff --git a/test/http/adapter/gun_test.exs b/test/http/adapter/gun_test.exs
new file mode 100644
index 000000000..a8dcbae04
--- /dev/null
+++ b/test/http/adapter/gun_test.exs
@@ -0,0 +1,267 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.Adapter.GunTest do
+ use ExUnit.Case, async: true
+ use Pleroma.Tests.Helpers
+ import ExUnit.CaptureLog
+ alias Pleroma.Config
+ alias Pleroma.Gun.Conn
+ alias Pleroma.HTTP.Adapter.Gun
+ alias Pleroma.Pool.Connections
+
+ setup_all do
+ {:ok, _} = Registry.start_link(keys: :unique, name: Pleroma.Gun.API.Mock)
+ :ok
+ end
+
+ describe "options/1" do
+ clear_config([:http, :adapter]) do
+ Config.put([:http, :adapter], a: 1, b: 2)
+ end
+
+ test "https url with default port" do
+ uri = URI.parse("https://example.com")
+
+ opts = Gun.options(uri)
+ assert opts[:certificates_verification]
+ tls_opts = opts[:tls_opts]
+ assert tls_opts[:verify] == :verify_peer
+ assert tls_opts[:depth] == 20
+ assert tls_opts[:reuse_sessions] == false
+
+ assert tls_opts[:verify_fun] ==
+ {&:ssl_verify_hostname.verify_fun/3, [check_hostname: 'example.com']}
+
+ assert File.exists?(tls_opts[:cacertfile])
+
+ assert opts[:original] == "example.com:443"
+ end
+
+ test "https ipv4 with default port" do
+ uri = URI.parse("https://127.0.0.1")
+
+ opts = Gun.options(uri)
+
+ assert opts[:tls_opts][:verify_fun] ==
+ {&:ssl_verify_hostname.verify_fun/3, [check_hostname: '127.0.0.1']}
+
+ assert opts[:original] == "127.0.0.1:443"
+ end
+
+ test "https ipv6 with default port" do
+ uri = URI.parse("https://[2a03:2880:f10c:83:face:b00c:0:25de]")
+
+ opts = Gun.options(uri)
+
+ assert opts[:tls_opts][:verify_fun] ==
+ {&:ssl_verify_hostname.verify_fun/3,
+ [check_hostname: '2a03:2880:f10c:83:face:b00c:0:25de']}
+
+ assert opts[:original] == "2a03:2880:f10c:83:face:b00c:0:25de:443"
+ end
+
+ test "https url with non standart port" do
+ uri = URI.parse("https://example.com:115")
+
+ opts = Gun.options(uri)
+
+ assert opts[:certificates_verification]
+ assert opts[:transport] == :tls
+ end
+
+ test "receive conn by default" do
+ uri = URI.parse("http://another-domain.com")
+ :ok = Conn.open(uri, :gun_connections)
+
+ received_opts = Gun.options(uri)
+ assert received_opts[:close_conn] == false
+ assert is_pid(received_opts[:conn])
+ end
+
+ test "don't receive conn if receive_conn is false" do
+ uri = URI.parse("http://another-domain2.com")
+ :ok = Conn.open(uri, :gun_connections)
+
+ opts = [receive_conn: false]
+ received_opts = Gun.options(opts, uri)
+ assert received_opts[:close_conn] == nil
+ assert received_opts[:conn] == nil
+ end
+
+ test "get conn on next request" do
+ level = Application.get_env(:logger, :level)
+ Logger.configure(level: :debug)
+ on_exit(fn -> Logger.configure(level: level) end)
+ uri = URI.parse("http://some-domain2.com")
+
+ assert capture_log(fn ->
+ opts = Gun.options(uri)
+
+ assert opts[:conn] == nil
+ assert opts[:close_conn] == nil
+ end) =~
+ "Gun connections pool checkin was not successful. Trying to open conn for next request."
+
+ opts = Gun.options(uri)
+
+ assert is_pid(opts[:conn])
+ assert opts[:close_conn] == false
+ end
+
+ test "merges with defaul http adapter config" do
+ defaults = Gun.options(URI.parse("https://example.com"))
+ assert Keyword.has_key?(defaults, :a)
+ assert Keyword.has_key?(defaults, :b)
+ end
+
+ test "default ssl adapter opts with connection" do
+ uri = URI.parse("https://some-domain.com")
+
+ :ok = Conn.open(uri, :gun_connections)
+
+ opts = Gun.options(uri)
+
+ assert opts[:certificates_verification]
+ tls_opts = opts[:tls_opts]
+ assert tls_opts[:verify] == :verify_peer
+ assert tls_opts[:depth] == 20
+ assert tls_opts[:reuse_sessions] == false
+
+ assert opts[:original] == "some-domain.com:443"
+ assert opts[:close_conn] == false
+ assert is_pid(opts[:conn])
+ end
+
+ test "parses string proxy host & port" do
+ proxy = Config.get([:http, :proxy_url])
+ Config.put([:http, :proxy_url], "localhost:8123")
+ on_exit(fn -> Config.put([:http, :proxy_url], proxy) end)
+
+ uri = URI.parse("https://some-domain.com")
+ opts = Gun.options([receive_conn: false], uri)
+ assert opts[:proxy] == {'localhost', 8123}
+ end
+
+ test "parses tuple proxy scheme host and port" do
+ proxy = Config.get([:http, :proxy_url])
+ Config.put([:http, :proxy_url], {:socks, 'localhost', 1234})
+ on_exit(fn -> Config.put([:http, :proxy_url], proxy) end)
+
+ uri = URI.parse("https://some-domain.com")
+ opts = Gun.options([receive_conn: false], uri)
+ assert opts[:proxy] == {:socks, 'localhost', 1234}
+ end
+
+ test "passed opts have more weight than defaults" do
+ proxy = Config.get([:http, :proxy_url])
+ Config.put([:http, :proxy_url], {:socks5, 'localhost', 1234})
+ on_exit(fn -> Config.put([:http, :proxy_url], proxy) end)
+ uri = URI.parse("https://some-domain.com")
+ opts = Gun.options([receive_conn: false, proxy: {'example.com', 4321}], uri)
+
+ assert opts[:proxy] == {'example.com', 4321}
+ end
+ end
+
+ describe "after_request/1" do
+ test "body_as not chunks" do
+ uri = URI.parse("http://some-domain.com")
+ :ok = Conn.open(uri, :gun_connections)
+ opts = Gun.options(uri)
+ :ok = Gun.after_request(opts)
+ conn = opts[:conn]
+
+ assert %Connections{
+ conns: %{
+ "http:some-domain.com:80" => %Pleroma.Gun.Conn{
+ conn: ^conn,
+ conn_state: :idle,
+ used_by: []
+ }
+ }
+ } = Connections.get_state(:gun_connections)
+ end
+
+ test "body_as chunks" do
+ uri = URI.parse("http://some-domain.com")
+ :ok = Conn.open(uri, :gun_connections)
+ opts = Gun.options([body_as: :chunks], uri)
+ :ok = Gun.after_request(opts)
+ conn = opts[:conn]
+ self = self()
+
+ assert %Connections{
+ conns: %{
+ "http:some-domain.com:80" => %Pleroma.Gun.Conn{
+ conn: ^conn,
+ conn_state: :active,
+ used_by: [{^self, _}]
+ }
+ }
+ } = Connections.get_state(:gun_connections)
+ end
+
+ test "with no connection" do
+ uri = URI.parse("http://uniq-domain.com")
+
+ :ok = Conn.open(uri, :gun_connections)
+
+ opts = Gun.options([body_as: :chunks], uri)
+ conn = opts[:conn]
+ opts = Keyword.delete(opts, :conn)
+ self = self()
+
+ :ok = Gun.after_request(opts)
+
+ assert %Connections{
+ conns: %{
+ "http:uniq-domain.com:80" => %Pleroma.Gun.Conn{
+ conn: ^conn,
+ conn_state: :active,
+ used_by: [{^self, _}]
+ }
+ }
+ } = Connections.get_state(:gun_connections)
+ end
+
+ test "with ipv4" do
+ uri = URI.parse("http://127.0.0.1")
+ :ok = Conn.open(uri, :gun_connections)
+ opts = Gun.options(uri)
+ send(:gun_connections, {:gun_up, opts[:conn], :http})
+ :ok = Gun.after_request(opts)
+ conn = opts[:conn]
+
+ assert %Connections{
+ conns: %{
+ "http:127.0.0.1:80" => %Pleroma.Gun.Conn{
+ conn: ^conn,
+ conn_state: :idle,
+ used_by: []
+ }
+ }
+ } = Connections.get_state(:gun_connections)
+ end
+
+ test "with ipv6" do
+ uri = URI.parse("http://[2a03:2880:f10c:83:face:b00c:0:25de]")
+ :ok = Conn.open(uri, :gun_connections)
+ opts = Gun.options(uri)
+ send(:gun_connections, {:gun_up, opts[:conn], :http})
+ :ok = Gun.after_request(opts)
+ conn = opts[:conn]
+
+ assert %Connections{
+ conns: %{
+ "http:2a03:2880:f10c:83:face:b00c:0:25de:80" => %Pleroma.Gun.Conn{
+ conn: ^conn,
+ conn_state: :idle,
+ used_by: []
+ }
+ }
+ } = Connections.get_state(:gun_connections)
+ end
+ end
+end
diff --git a/test/http/adapter/hackney_test.exs b/test/http/adapter/hackney_test.exs
new file mode 100644
index 000000000..35cb58125
--- /dev/null
+++ b/test/http/adapter/hackney_test.exs
@@ -0,0 +1,54 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.Adapter.HackneyTest do
+ use ExUnit.Case
+ use Pleroma.Tests.Helpers
+
+ alias Pleroma.Config
+ alias Pleroma.HTTP.Adapter.Hackney
+
+ setup_all do
+ uri = URI.parse("http://domain.com")
+ {:ok, uri: uri}
+ end
+
+ describe "options/2" do
+ clear_config([:http, :adapter]) do
+ Config.put([:http, :adapter], a: 1, b: 2)
+ end
+
+ test "add proxy and opts from config", %{uri: uri} do
+ proxy = Config.get([:http, :proxy_url])
+ Config.put([:http, :proxy_url], "localhost:8123")
+ on_exit(fn -> Config.put([:http, :proxy_url], proxy) end)
+
+ opts = Hackney.options(uri)
+
+ assert opts[:a] == 1
+ assert opts[:b] == 2
+ assert opts[:proxy] == "localhost:8123"
+ end
+
+ test "respect connection opts and no proxy", %{uri: uri} do
+ opts = Hackney.options([a: 2, b: 1], uri)
+
+ assert opts[:a] == 2
+ assert opts[:b] == 1
+ refute Keyword.has_key?(opts, :proxy)
+ end
+
+ test "add opts for https" do
+ uri = URI.parse("https://domain.com")
+
+ opts = Hackney.options(uri)
+
+ assert opts[:ssl_options] == [
+ partial_chain: &:hackney_connect.partial_chain/1,
+ versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
+ server_name_indication: 'domain.com'
+ ]
+ end
+ end
+end
diff --git a/test/http/adapter_test.exs b/test/http/adapter_test.exs
new file mode 100644
index 000000000..37e47dabe
--- /dev/null
+++ b/test/http/adapter_test.exs
@@ -0,0 +1,65 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.AdapterTest do
+ use ExUnit.Case, async: true
+
+ alias Pleroma.HTTP.Adapter
+
+ describe "domain_or_ip/1" do
+ test "with domain" do
+ assert Adapter.domain_or_ip("example.com") == {:domain, 'example.com'}
+ end
+
+ test "with idna domain" do
+ assert Adapter.domain_or_ip("ですexample.com") == {:domain, 'xn--example-183fne.com'}
+ end
+
+ test "with ipv4" do
+ assert Adapter.domain_or_ip("127.0.0.1") == {:ip, {127, 0, 0, 1}}
+ end
+
+ test "with ipv6" do
+ assert Adapter.domain_or_ip("2a03:2880:f10c:83:face:b00c:0:25de") ==
+ {:ip, {10_755, 10_368, 61_708, 131, 64_206, 45_068, 0, 9_694}}
+ end
+ end
+
+ describe "domain_or_fallback/1" do
+ test "with domain" do
+ assert Adapter.domain_or_fallback("example.com") == 'example.com'
+ end
+
+ test "with idna domain" do
+ assert Adapter.domain_or_fallback("ですexample.com") == 'xn--example-183fne.com'
+ end
+
+ test "with ipv4" do
+ assert Adapter.domain_or_fallback("127.0.0.1") == '127.0.0.1'
+ end
+
+ test "with ipv6" do
+ assert Adapter.domain_or_fallback("2a03:2880:f10c:83:face:b00c:0:25de") ==
+ '2a03:2880:f10c:83:face:b00c:0:25de'
+ end
+ end
+
+ describe "format_proxy/1" do
+ test "with nil" do
+ assert Adapter.format_proxy(nil) == nil
+ end
+
+ test "with string" do
+ assert Adapter.format_proxy("127.0.0.1:8123") == {{127, 0, 0, 1}, 8123}
+ end
+
+ test "localhost with port" do
+ assert Adapter.format_proxy("localhost:8123") == {'localhost', 8123}
+ end
+
+ test "tuple" do
+ assert Adapter.format_proxy({:socks4, :localhost, 9050}) == {:socks4, 'localhost', 9050}
+ end
+ end
+end
diff --git a/test/http/connection_test.exs b/test/http/connection_test.exs
new file mode 100644
index 000000000..53ccbc9cd
--- /dev/null
+++ b/test/http/connection_test.exs
@@ -0,0 +1,142 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.ConnectionTest do
+ use ExUnit.Case
+ use Pleroma.Tests.Helpers
+ import ExUnit.CaptureLog
+ alias Pleroma.Config
+ alias Pleroma.HTTP.Connection
+
+ setup_all do
+ {:ok, _} = Registry.start_link(keys: :unique, name: Pleroma.Gun.API.Mock)
+ :ok
+ end
+
+ describe "parse_host/1" do
+ test "as atom to charlist" do
+ assert Connection.parse_host(:localhost) == 'localhost'
+ end
+
+ test "as string to charlist" do
+ assert Connection.parse_host("localhost.com") == 'localhost.com'
+ end
+
+ test "as string ip to tuple" do
+ assert Connection.parse_host("127.0.0.1") == {127, 0, 0, 1}
+ end
+ end
+
+ describe "parse_proxy/1" do
+ test "ip with port" do
+ assert Connection.parse_proxy("127.0.0.1:8123") == {:ok, {127, 0, 0, 1}, 8123}
+ end
+
+ test "host with port" do
+ assert Connection.parse_proxy("localhost:8123") == {:ok, 'localhost', 8123}
+ end
+
+ test "as tuple" do
+ assert Connection.parse_proxy({:socks4, :localhost, 9050}) ==
+ {:ok, :socks4, 'localhost', 9050}
+ end
+
+ test "as tuple with string host" do
+ assert Connection.parse_proxy({:socks5, "localhost", 9050}) ==
+ {:ok, :socks5, 'localhost', 9050}
+ end
+ end
+
+ describe "parse_proxy/1 errors" do
+ test "ip without port" do
+ capture_log(fn ->
+ assert Connection.parse_proxy("127.0.0.1") == {:error, :error_parsing_proxy}
+ end) =~ "parsing proxy fail \"127.0.0.1\""
+ end
+
+ test "host without port" do
+ capture_log(fn ->
+ assert Connection.parse_proxy("localhost") == {:error, :error_parsing_proxy}
+ end) =~ "parsing proxy fail \"localhost\""
+ end
+
+ test "host with bad port" do
+ capture_log(fn ->
+ assert Connection.parse_proxy("localhost:port") == {:error, :error_parsing_port_in_proxy}
+ end) =~ "parsing port in proxy fail \"localhost:port\""
+ end
+
+ test "ip with bad port" do
+ capture_log(fn ->
+ assert Connection.parse_proxy("127.0.0.1:15.9") == {:error, :error_parsing_port_in_proxy}
+ end) =~ "parsing port in proxy fail \"127.0.0.1:15.9\""
+ end
+
+ test "as tuple without port" do
+ capture_log(fn ->
+ assert Connection.parse_proxy({:socks5, :localhost}) == {:error, :error_parsing_proxy}
+ end) =~ "parsing proxy fail {:socks5, :localhost}"
+ end
+
+ test "with nil" do
+ assert Connection.parse_proxy(nil) == nil
+ end
+ end
+
+ describe "options/3" do
+ clear_config([:http, :proxy_url])
+
+ test "without proxy_url in config" do
+ Config.delete([:http, :proxy_url])
+
+ opts = Connection.options(%URI{})
+ refute Keyword.has_key?(opts, :proxy)
+ end
+
+ test "parses string proxy host & port" do
+ Config.put([:http, :proxy_url], "localhost:8123")
+
+ opts = Connection.options(%URI{})
+ assert opts[:proxy] == {'localhost', 8123}
+ end
+
+ test "parses tuple proxy scheme host and port" do
+ Config.put([:http, :proxy_url], {:socks, 'localhost', 1234})
+
+ opts = Connection.options(%URI{})
+ assert opts[:proxy] == {:socks, 'localhost', 1234}
+ end
+
+ test "passed opts have more weight than defaults" do
+ Config.put([:http, :proxy_url], {:socks5, 'localhost', 1234})
+
+ opts = Connection.options(%URI{}, proxy: {'example.com', 4321})
+
+ assert opts[:proxy] == {'example.com', 4321}
+ end
+
+ test "default ssl adapter opts with connection" do
+ adapter = Application.get_env(:tesla, :adapter)
+ Application.put_env(:tesla, :adapter, Tesla.Adapter.Gun)
+ on_exit(fn -> Application.put_env(:tesla, :adapter, adapter) end)
+
+ uri = URI.parse("https://some-domain.com")
+
+ pid = Process.whereis(:federation)
+ :ok = Pleroma.Gun.Conn.open(uri, :gun_connections, genserver_pid: pid)
+
+ opts = Connection.options(uri)
+
+ assert opts[:certificates_verification]
+ tls_opts = opts[:tls_opts]
+ assert tls_opts[:verify] == :verify_peer
+ assert tls_opts[:depth] == 20
+ assert tls_opts[:reuse_sessions] == false
+
+ assert opts[:original] == "some-domain.com:443"
+ assert opts[:close_conn] == false
+ assert is_pid(opts[:conn])
+ end
+ end
+end
diff --git a/test/http/request_builder_test.exs b/test/http/request_builder_test.exs
index 113158c9f..70946a932 100644
--- a/test/http/request_builder_test.exs
+++ b/test/http/request_builder_test.exs
@@ -5,6 +5,8 @@
defmodule Pleroma.HTTP.RequestBuilderTest do
use ExUnit.Case, async: true
use Pleroma.Tests.Helpers
+ alias Pleroma.Config
+ alias Pleroma.HTTP.Request
alias Pleroma.HTTP.RequestBuilder
describe "headers/2" do
@@ -12,24 +14,24 @@ defmodule Pleroma.HTTP.RequestBuilderTest do
clear_config([:http, :user_agent])
test "don't send pleroma user agent" do
- assert RequestBuilder.headers(%{}, []) == %{headers: []}
+ assert RequestBuilder.headers(%Request{}, []) == %Request{headers: []}
end
test "send pleroma user agent" do
- Pleroma.Config.put([:http, :send_user_agent], true)
- Pleroma.Config.put([:http, :user_agent], :default)
+ Config.put([:http, :send_user_agent], true)
+ Config.put([:http, :user_agent], :default)
- assert RequestBuilder.headers(%{}, []) == %{
- headers: [{"User-Agent", Pleroma.Application.user_agent()}]
+ assert RequestBuilder.headers(%Request{}, []) == %Request{
+ headers: [{"user-agent", Pleroma.Application.user_agent()}]
}
end
test "send custom user agent" do
- Pleroma.Config.put([:http, :send_user_agent], true)
- Pleroma.Config.put([:http, :user_agent], "totally-not-pleroma")
+ Config.put([:http, :send_user_agent], true)
+ Config.put([:http, :user_agent], "totally-not-pleroma")
- assert RequestBuilder.headers(%{}, []) == %{
- headers: [{"User-Agent", "totally-not-pleroma"}]
+ assert RequestBuilder.headers(%Request{}, []) == %Request{
+ headers: [{"user-agent", "totally-not-pleroma"}]
}
end
end
@@ -41,19 +43,19 @@ defmodule Pleroma.HTTP.RequestBuilderTest do
test "add query parameter" do
assert RequestBuilder.add_optional_params(
- %{},
+ %Request{},
%{query: :query, body: :body, another: :val},
[
{:query, "param1=val1&param2=val2"},
{:body, "some body"}
]
- ) == %{query: "param1=val1&param2=val2", body: "some body"}
+ ) == %Request{query: "param1=val1&param2=val2", body: "some body"}
end
end
describe "add_param/4" do
test "add file parameter" do
- %{
+ %Request{
body: %Tesla.Multipart{
boundary: _,
content_type_params: [],
@@ -70,7 +72,7 @@ defmodule Pleroma.HTTP.RequestBuilderTest do
}
]
}
- } = RequestBuilder.add_param(%{}, :file, "filename.png", "some-path/filename.png")
+ } = RequestBuilder.add_param(%Request{}, :file, "filename.png", "some-path/filename.png")
end
test "add key to body" do
@@ -82,7 +84,7 @@ defmodule Pleroma.HTTP.RequestBuilderTest do
%Tesla.Multipart.Part{
body: "\"someval\"",
dispositions: [name: "somekey"],
- headers: ["Content-Type": "application/json"]
+ headers: [{"content-type", "application/json"}]
}
]
}
diff --git a/test/http_test.exs b/test/http_test.exs
index 5f9522cf0..83c27f6e1 100644
--- a/test/http_test.exs
+++ b/test/http_test.exs
@@ -3,8 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTPTest do
- use Pleroma.DataCase
+ use ExUnit.Case
+ use Pleroma.Tests.Helpers
import Tesla.Mock
+ alias Pleroma.HTTP
setup do
mock(fn
@@ -27,7 +29,7 @@ defmodule Pleroma.HTTPTest do
describe "get/1" do
test "returns successfully result" do
- assert Pleroma.HTTP.get("http://example.com/hello") == {
+ assert HTTP.get("http://example.com/hello") == {
:ok,
%Tesla.Env{status: 200, body: "hello"}
}
@@ -36,7 +38,7 @@ defmodule Pleroma.HTTPTest do
describe "get/2 (with headers)" do
test "returns successfully result for json content-type" do
- assert Pleroma.HTTP.get("http://example.com/hello", [{"content-type", "application/json"}]) ==
+ assert HTTP.get("http://example.com/hello", [{"content-type", "application/json"}]) ==
{
:ok,
%Tesla.Env{
@@ -50,10 +52,35 @@ defmodule Pleroma.HTTPTest do
describe "post/2" do
test "returns successfully result" do
- assert Pleroma.HTTP.post("http://example.com/world", "") == {
+ assert HTTP.post("http://example.com/world", "") == {
:ok,
%Tesla.Env{status: 200, body: "world"}
}
end
end
+
+ describe "connection pools" do
+ @describetag :integration
+ clear_config(Pleroma.Gun.API) do
+ Pleroma.Config.put(Pleroma.Gun.API, Pleroma.Gun)
+ end
+
+ test "gun" do
+ adapter = Application.get_env(:tesla, :adapter)
+ Application.put_env(:tesla, :adapter, Tesla.Adapter.Gun)
+
+ on_exit(fn ->
+ Application.put_env(:tesla, :adapter, adapter)
+ end)
+
+ options = [adapter: [pool: :federation]]
+
+ assert {:ok, resp} = HTTP.get("https://httpbin.org/user-agent", [], options)
+
+ assert resp.status == 200
+
+ state = Pleroma.Pool.Connections.get_state(:gun_connections)
+ assert state.conns["https:httpbin.org:443"]
+ end
+ end
end
diff --git a/test/notification_test.exs b/test/notification_test.exs
index 04bf5b41a..1de3c6e3b 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -649,6 +649,13 @@ defmodule Pleroma.NotificationTest do
"object" => remote_user.ap_id
}
+ remote_user_url = remote_user.ap_id
+
+ Tesla.Mock.mock(fn
+ %{method: :get, url: ^remote_user_url} ->
+ %Tesla.Env{status: 404, body: ""}
+ end)
+
{:ok, _delete_activity} = Transmogrifier.handle_incoming(delete_user_message)
ObanHelpers.perform_all()
diff --git a/test/otp_version_test.exs b/test/otp_version_test.exs
new file mode 100644
index 000000000..f26b90f61
--- /dev/null
+++ b/test/otp_version_test.exs
@@ -0,0 +1,58 @@
+defmodule Pleroma.OTPVersionTest do
+ use ExUnit.Case, async: true
+
+ alias Pleroma.OTPVersion
+
+ describe "get_and_check_version/2" do
+ test "22.4" do
+ assert OTPVersion.get_and_check_version(Tesla.Adapter.Gun, [
+ "test/fixtures/warnings/otp_version/22.4"
+ ]) == :ok
+ end
+
+ test "22.1" do
+ assert OTPVersion.get_and_check_version(Tesla.Adapter.Gun, [
+ "test/fixtures/warnings/otp_version/22.1"
+ ]) == {:error, "22.1"}
+ end
+
+ test "21.1" do
+ assert OTPVersion.get_and_check_version(Tesla.Adapter.Gun, [
+ "test/fixtures/warnings/otp_version/21.1"
+ ]) == {:error, "21.1"}
+ end
+
+ test "23.0" do
+ assert OTPVersion.get_and_check_version(Tesla.Adapter.Gun, [
+ "test/fixtures/warnings/otp_version/23.0"
+ ]) == :ok
+ end
+
+ test "undefined" do
+ assert OTPVersion.get_and_check_version(Tesla.Adapter.Gun, [
+ "test/fixtures/warnings/otp_version/undefined"
+ ]) == :undefined
+ end
+
+ test "not parsable" do
+ assert OTPVersion.get_and_check_version(Tesla.Adapter.Gun, [
+ "test/fixtures/warnings/otp_version/error"
+ ]) == :undefined
+ end
+
+ test "with non existance file" do
+ assert OTPVersion.get_and_check_version(Tesla.Adapter.Gun, [
+ "test/fixtures/warnings/otp_version/non-exising",
+ "test/fixtures/warnings/otp_version/22.4"
+ ]) == :ok
+ end
+
+ test "empty paths" do
+ assert OTPVersion.get_and_check_version(Tesla.Adapter.Gun, []) == :undefined
+ end
+
+ test "another adapter" do
+ assert OTPVersion.get_and_check_version(Tesla.Adapter.Hackney, []) == :ok
+ end
+ end
+end
diff --git a/test/pool/connections_test.exs b/test/pool/connections_test.exs
new file mode 100644
index 000000000..f766e3b5f
--- /dev/null
+++ b/test/pool/connections_test.exs
@@ -0,0 +1,977 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Pool.ConnectionsTest do
+ use ExUnit.Case
+ use Pleroma.Tests.Helpers
+ import ExUnit.CaptureLog
+ alias Pleroma.Gun.API
+ alias Pleroma.Gun.Conn
+ alias Pleroma.Pool.Connections
+
+ setup_all do
+ {:ok, _} = Registry.start_link(keys: :unique, name: API.Mock)
+ :ok
+ end
+
+ clear_config([:connections_pool, :retry]) do
+ Pleroma.Config.put([:connections_pool, :retry], 5)
+ end
+
+ setup do
+ name = :test_connections
+ adapter = Application.get_env(:tesla, :adapter)
+ Application.put_env(:tesla, :adapter, Tesla.Adapter.Gun)
+ on_exit(fn -> Application.put_env(:tesla, :adapter, adapter) end)
+
+ {:ok, _pid} =
+ Connections.start_link({name, [max_connections: 2, receive_connection_timeout: 1_500]})
+
+ {:ok, name: name}
+ end
+
+ describe "alive?/2" do
+ test "is alive", %{name: name} do
+ assert Connections.alive?(name)
+ end
+
+ test "returns false if not started" do
+ refute Connections.alive?(:some_random_name)
+ end
+ end
+
+ test "opens connection and reuse it on next request", %{name: name} do
+ url = "http://some-domain.com"
+ key = "http:some-domain.com:80"
+ refute Connections.checkin(url, name)
+ :ok = Conn.open(url, name)
+
+ conn = Connections.checkin(url, name)
+ assert is_pid(conn)
+ assert Process.alive?(conn)
+
+ self = self()
+
+ %Connections{
+ conns: %{
+ ^key => %Conn{
+ conn: ^conn,
+ gun_state: :up,
+ used_by: [{^self, _}],
+ conn_state: :active
+ }
+ }
+ } = Connections.get_state(name)
+
+ reused_conn = Connections.checkin(url, name)
+
+ assert conn == reused_conn
+
+ %Connections{
+ conns: %{
+ ^key => %Conn{
+ conn: ^conn,
+ gun_state: :up,
+ used_by: [{^self, _}, {^self, _}],
+ conn_state: :active
+ }
+ }
+ } = Connections.get_state(name)
+
+ :ok = Connections.checkout(conn, self, name)
+
+ %Connections{
+ conns: %{
+ ^key => %Conn{
+ conn: ^conn,
+ gun_state: :up,
+ used_by: [{^self, _}],
+ conn_state: :active
+ }
+ }
+ } = Connections.get_state(name)
+
+ :ok = Connections.checkout(conn, self, name)
+
+ %Connections{
+ conns: %{
+ ^key => %Conn{
+ conn: ^conn,
+ gun_state: :up,
+ used_by: [],
+ conn_state: :idle
+ }
+ }
+ } = Connections.get_state(name)
+ end
+
+ test "reuse connection for idna domains", %{name: name} do
+ url = "http://ですsome-domain.com"
+ refute Connections.checkin(url, name)
+
+ :ok = Conn.open(url, name)
+
+ conn = Connections.checkin(url, name)
+ assert is_pid(conn)
+ assert Process.alive?(conn)
+
+ self = self()
+
+ %Connections{
+ conns: %{
+ "http:ですsome-domain.com:80" => %Conn{
+ conn: ^conn,
+ gun_state: :up,
+ used_by: [{^self, _}],
+ conn_state: :active
+ }
+ }
+ } = Connections.get_state(name)
+
+ reused_conn = Connections.checkin(url, name)
+
+ assert conn == reused_conn
+ end
+
+ test "reuse for ipv4", %{name: name} do
+ url = "http://127.0.0.1"
+
+ refute Connections.checkin(url, name)
+
+ :ok = Conn.open(url, name)
+
+ conn = Connections.checkin(url, name)
+ assert is_pid(conn)
+ assert Process.alive?(conn)
+
+ self = self()
+
+ %Connections{
+ conns: %{
+ "http:127.0.0.1:80" => %Conn{
+ conn: ^conn,
+ gun_state: :up,
+ used_by: [{^self, _}],
+ conn_state: :active
+ }
+ }
+ } = Connections.get_state(name)
+
+ reused_conn = Connections.checkin(url, name)
+
+ assert conn == reused_conn
+
+ :ok = Connections.checkout(conn, self, name)
+ :ok = Connections.checkout(reused_conn, self, name)
+
+ %Connections{
+ conns: %{
+ "http:127.0.0.1:80" => %Conn{
+ conn: ^conn,
+ gun_state: :up,
+ used_by: [],
+ conn_state: :idle
+ }
+ }
+ } = Connections.get_state(name)
+ end
+
+ test "reuse for ipv6", %{name: name} do
+ url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]"
+
+ refute Connections.checkin(url, name)
+
+ :ok = Conn.open(url, name)
+
+ conn = Connections.checkin(url, name)
+ assert is_pid(conn)
+ assert Process.alive?(conn)
+
+ self = self()
+
+ %Connections{
+ conns: %{
+ "http:2a03:2880:f10c:83:face:b00c:0:25de:80" => %Conn{
+ conn: ^conn,
+ gun_state: :up,
+ used_by: [{^self, _}],
+ conn_state: :active
+ }
+ }
+ } = Connections.get_state(name)
+
+ reused_conn = Connections.checkin(url, name)
+
+ assert conn == reused_conn
+ end
+
+ test "up and down ipv4", %{name: name} do
+ self = self()
+ url = "http://127.0.0.1"
+ :ok = Conn.open(url, name)
+ conn = Connections.checkin(url, name)
+ send(name, {:gun_down, conn, nil, nil, nil})
+ send(name, {:gun_up, conn, nil})
+
+ %Connections{
+ conns: %{
+ "http:127.0.0.1:80" => %Conn{
+ conn: ^conn,
+ gun_state: :up,
+ used_by: [{^self, _}],
+ conn_state: :active
+ }
+ }
+ } = Connections.get_state(name)
+ end
+
+ test "up and down ipv6", %{name: name} do
+ self = self()
+ url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]"
+ :ok = Conn.open(url, name)
+ conn = Connections.checkin(url, name)
+ send(name, {:gun_down, conn, nil, nil, nil})
+ send(name, {:gun_up, conn, nil})
+
+ %Connections{
+ conns: %{
+ "http:2a03:2880:f10c:83:face:b00c:0:25de:80" => %Conn{
+ conn: ^conn,
+ gun_state: :up,
+ used_by: [{^self, _}],
+ conn_state: :active
+ }
+ }
+ } = Connections.get_state(name)
+ end
+
+ test "reuses connection based on protocol", %{name: name} do
+ http_url = "http://some-domain.com"
+ http_key = "http:some-domain.com:80"
+ https_url = "https://some-domain.com"
+ https_key = "https:some-domain.com:443"
+
+ refute Connections.checkin(http_url, name)
+ :ok = Conn.open(http_url, name)
+ conn = Connections.checkin(http_url, name)
+ assert is_pid(conn)
+ assert Process.alive?(conn)
+
+ refute Connections.checkin(https_url, name)
+ :ok = Conn.open(https_url, name)
+ https_conn = Connections.checkin(https_url, name)
+
+ refute conn == https_conn
+
+ reused_https = Connections.checkin(https_url, name)
+
+ refute conn == reused_https
+
+ assert reused_https == https_conn
+
+ %Connections{
+ conns: %{
+ ^http_key => %Conn{
+ conn: ^conn,
+ gun_state: :up
+ },
+ ^https_key => %Conn{
+ conn: ^https_conn,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+ end
+
+ test "connection can't get up", %{name: name} do
+ url = "http://gun-not-up.com"
+
+ assert capture_log(fn ->
+ refute Conn.open(url, name)
+ refute Connections.checkin(url, name)
+ end) =~
+ "Received error on opening connection http://gun-not-up.com {:error, :timeout}"
+ end
+
+ test "process gun_down message and then gun_up", %{name: name} do
+ self = self()
+ url = "http://gun-down-and-up.com"
+ key = "http:gun-down-and-up.com:80"
+ :ok = Conn.open(url, name)
+ conn = Connections.checkin(url, name)
+
+ assert is_pid(conn)
+ assert Process.alive?(conn)
+
+ %Connections{
+ conns: %{
+ ^key => %Conn{
+ conn: ^conn,
+ gun_state: :up,
+ used_by: [{^self, _}]
+ }
+ }
+ } = Connections.get_state(name)
+
+ send(name, {:gun_down, conn, :http, nil, nil})
+
+ %Connections{
+ conns: %{
+ ^key => %Conn{
+ conn: ^conn,
+ gun_state: :down,
+ used_by: [{^self, _}]
+ }
+ }
+ } = Connections.get_state(name)
+
+ send(name, {:gun_up, conn, :http})
+
+ conn2 = Connections.checkin(url, name)
+ assert conn == conn2
+
+ assert is_pid(conn2)
+ assert Process.alive?(conn2)
+
+ %Connections{
+ conns: %{
+ ^key => %Conn{
+ conn: _,
+ gun_state: :up,
+ used_by: [{^self, _}, {^self, _}]
+ }
+ }
+ } = Connections.get_state(name)
+ end
+
+ test "async processes get same conn for same domain", %{name: name} do
+ url = "http://some-domain.com"
+ :ok = Conn.open(url, name)
+
+ tasks =
+ for _ <- 1..5 do
+ Task.async(fn ->
+ Connections.checkin(url, name)
+ end)
+ end
+
+ tasks_with_results = Task.yield_many(tasks)
+
+ results =
+ Enum.map(tasks_with_results, fn {task, res} ->
+ res || Task.shutdown(task, :brutal_kill)
+ end)
+
+ conns = for {:ok, value} <- results, do: value
+
+ %Connections{
+ conns: %{
+ "http:some-domain.com:80" => %Conn{
+ conn: conn,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+
+ assert Enum.all?(conns, fn res -> res == conn end)
+ end
+
+ test "remove frequently used and idle", %{name: name} do
+ self = self()
+ http_url = "http://some-domain.com"
+ https_url = "https://some-domain.com"
+ :ok = Conn.open(https_url, name)
+ :ok = Conn.open(http_url, name)
+
+ conn1 = Connections.checkin(https_url, name)
+
+ [conn2 | _conns] =
+ for _ <- 1..4 do
+ Connections.checkin(http_url, name)
+ end
+
+ http_key = "http:some-domain.com:80"
+
+ %Connections{
+ conns: %{
+ ^http_key => %Conn{
+ conn: ^conn2,
+ gun_state: :up,
+ conn_state: :active,
+ used_by: [{^self, _}, {^self, _}, {^self, _}, {^self, _}]
+ },
+ "https:some-domain.com:443" => %Conn{
+ conn: ^conn1,
+ gun_state: :up,
+ conn_state: :active,
+ used_by: [{^self, _}]
+ }
+ }
+ } = Connections.get_state(name)
+
+ :ok = Connections.checkout(conn1, self, name)
+
+ another_url = "http://another-domain.com"
+ :ok = Conn.open(another_url, name)
+ conn = Connections.checkin(another_url, name)
+
+ %Connections{
+ conns: %{
+ "http:another-domain.com:80" => %Conn{
+ conn: ^conn,
+ gun_state: :up
+ },
+ ^http_key => %Conn{
+ conn: _,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+ end
+
+ describe "integration test" do
+ @describetag :integration
+
+ clear_config(API) do
+ Pleroma.Config.put(API, Pleroma.Gun)
+ end
+
+ test "opens connection and change owner", %{name: name} do
+ url = "https://httpbin.org"
+ :ok = Conn.open(url, name)
+ conn = Connections.checkin(url, name)
+
+ pid = Process.whereis(name)
+
+ assert :gun.info(conn).owner == pid
+ end
+
+ test "opens connection and reuse it on next request", %{name: name} do
+ url = "http://httpbin.org"
+ :ok = Conn.open(url, name)
+ Process.sleep(250)
+ conn = Connections.checkin(url, name)
+
+ assert is_pid(conn)
+ assert Process.alive?(conn)
+
+ reused_conn = Connections.checkin(url, name)
+
+ assert conn == reused_conn
+
+ %Connections{
+ conns: %{
+ "http:httpbin.org:80" => %Conn{
+ conn: ^conn,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+ end
+
+ test "opens ssl connection and reuse it on next request", %{name: name} do
+ url = "https://httpbin.org"
+ :ok = Conn.open(url, name)
+ Process.sleep(1_000)
+ conn = Connections.checkin(url, name)
+
+ assert is_pid(conn)
+ assert Process.alive?(conn)
+
+ reused_conn = Connections.checkin(url, name)
+
+ assert conn == reused_conn
+
+ %Connections{
+ conns: %{
+ "https:httpbin.org:443" => %Conn{
+ conn: ^conn,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+ end
+
+ test "remove frequently used and idle", %{name: name} do
+ self = self()
+ https1 = "https://www.google.com"
+ https2 = "https://httpbin.org"
+
+ :ok = Conn.open(https1, name)
+ :ok = Conn.open(https2, name)
+ Process.sleep(1_500)
+ conn = Connections.checkin(https1, name)
+
+ for _ <- 1..4 do
+ Connections.checkin(https2, name)
+ end
+
+ %Connections{
+ conns: %{
+ "https:httpbin.org:443" => %Conn{
+ conn: _,
+ gun_state: :up
+ },
+ "https:www.google.com:443" => %Conn{
+ conn: _,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+
+ :ok = Connections.checkout(conn, self, name)
+ http = "http://httpbin.org"
+ Process.sleep(1_000)
+ :ok = Conn.open(http, name)
+ conn = Connections.checkin(http, name)
+
+ %Connections{
+ conns: %{
+ "http:httpbin.org:80" => %Conn{
+ conn: ^conn,
+ gun_state: :up
+ },
+ "https:httpbin.org:443" => %Conn{
+ conn: _,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+ end
+
+ test "remove earlier used and idle", %{name: name} do
+ self = self()
+
+ https1 = "https://www.google.com"
+ https2 = "https://httpbin.org"
+ :ok = Conn.open(https1, name)
+ :ok = Conn.open(https2, name)
+ Process.sleep(1_500)
+
+ Connections.checkin(https1, name)
+ conn = Connections.checkin(https1, name)
+
+ Process.sleep(1_000)
+ Connections.checkin(https2, name)
+ Connections.checkin(https2, name)
+
+ %Connections{
+ conns: %{
+ "https:httpbin.org:443" => %Conn{
+ conn: _,
+ gun_state: :up
+ },
+ "https:www.google.com:443" => %Conn{
+ conn: ^conn,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+
+ :ok = Connections.checkout(conn, self, name)
+ :ok = Connections.checkout(conn, self, name)
+
+ http = "http://httpbin.org"
+ :ok = Conn.open(http, name)
+ Process.sleep(1_000)
+
+ conn = Connections.checkin(http, name)
+
+ %Connections{
+ conns: %{
+ "http:httpbin.org:80" => %Conn{
+ conn: ^conn,
+ gun_state: :up
+ },
+ "https:httpbin.org:443" => %Conn{
+ conn: _,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+ end
+
+ test "doesn't open new conn on pool overflow", %{name: name} do
+ self = self()
+
+ https1 = "https://www.google.com"
+ https2 = "https://httpbin.org"
+ :ok = Conn.open(https1, name)
+ :ok = Conn.open(https2, name)
+ Process.sleep(1_000)
+ Connections.checkin(https1, name)
+ conn1 = Connections.checkin(https1, name)
+ conn2 = Connections.checkin(https2, name)
+
+ %Connections{
+ conns: %{
+ "https:httpbin.org:443" => %Conn{
+ conn: ^conn2,
+ gun_state: :up,
+ conn_state: :active,
+ used_by: [{^self, _}]
+ },
+ "https:www.google.com:443" => %Conn{
+ conn: ^conn1,
+ gun_state: :up,
+ conn_state: :active,
+ used_by: [{^self, _}, {^self, _}]
+ }
+ }
+ } = Connections.get_state(name)
+
+ refute Connections.checkin("http://httpbin.org", name)
+
+ %Connections{
+ conns: %{
+ "https:httpbin.org:443" => %Conn{
+ conn: ^conn2,
+ gun_state: :up,
+ conn_state: :active,
+ used_by: [{^self, _}]
+ },
+ "https:www.google.com:443" => %Conn{
+ conn: ^conn1,
+ gun_state: :up,
+ conn_state: :active,
+ used_by: [{^self, _}, {^self, _}]
+ }
+ }
+ } = Connections.get_state(name)
+ end
+
+ test "get idle connection with the smallest crf", %{
+ name: name
+ } do
+ self = self()
+
+ https1 = "https://www.google.com"
+ https2 = "https://httpbin.org"
+
+ :ok = Conn.open(https1, name)
+ :ok = Conn.open(https2, name)
+ Process.sleep(1_500)
+ Connections.checkin(https1, name)
+ Connections.checkin(https2, name)
+ Connections.checkin(https1, name)
+ conn1 = Connections.checkin(https1, name)
+ conn2 = Connections.checkin(https2, name)
+
+ %Connections{
+ conns: %{
+ "https:httpbin.org:443" => %Conn{
+ conn: ^conn2,
+ gun_state: :up,
+ conn_state: :active,
+ used_by: [{^self, _}, {^self, _}],
+ crf: crf2
+ },
+ "https:www.google.com:443" => %Conn{
+ conn: ^conn1,
+ gun_state: :up,
+ conn_state: :active,
+ used_by: [{^self, _}, {^self, _}, {^self, _}],
+ crf: crf1
+ }
+ }
+ } = Connections.get_state(name)
+
+ assert crf1 > crf2
+
+ :ok = Connections.checkout(conn1, self, name)
+ :ok = Connections.checkout(conn1, self, name)
+ :ok = Connections.checkout(conn1, self, name)
+
+ :ok = Connections.checkout(conn2, self, name)
+ :ok = Connections.checkout(conn2, self, name)
+
+ %Connections{
+ conns: %{
+ "https:httpbin.org:443" => %Conn{
+ conn: ^conn2,
+ gun_state: :up,
+ conn_state: :idle,
+ used_by: []
+ },
+ "https:www.google.com:443" => %Conn{
+ conn: ^conn1,
+ gun_state: :up,
+ conn_state: :idle,
+ used_by: []
+ }
+ }
+ } = Connections.get_state(name)
+
+ http = "http://httpbin.org"
+ :ok = Conn.open(http, name)
+ Process.sleep(1_000)
+ conn = Connections.checkin(http, name)
+
+ %Connections{
+ conns: %{
+ "https:www.google.com:443" => %Conn{
+ conn: ^conn1,
+ gun_state: :up,
+ conn_state: :idle,
+ used_by: [],
+ crf: crf1
+ },
+ "http:httpbin.org:80" => %Conn{
+ conn: ^conn,
+ gun_state: :up,
+ conn_state: :active,
+ used_by: [{^self, _}],
+ crf: crf
+ }
+ }
+ } = Connections.get_state(name)
+
+ assert crf1 > crf
+ end
+ end
+
+ describe "with proxy" do
+ test "as ip", %{name: name} do
+ url = "http://proxy-string.com"
+ key = "http:proxy-string.com:80"
+ :ok = Conn.open(url, name, proxy: {{127, 0, 0, 1}, 8123})
+
+ conn = Connections.checkin(url, name)
+
+ %Connections{
+ conns: %{
+ ^key => %Conn{
+ conn: ^conn,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+
+ reused_conn = Connections.checkin(url, name)
+
+ assert reused_conn == conn
+ end
+
+ test "as host", %{name: name} do
+ url = "http://proxy-tuple-atom.com"
+ :ok = Conn.open(url, name, proxy: {'localhost', 9050})
+ conn = Connections.checkin(url, name)
+
+ %Connections{
+ conns: %{
+ "http:proxy-tuple-atom.com:80" => %Conn{
+ conn: ^conn,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+
+ reused_conn = Connections.checkin(url, name)
+
+ assert reused_conn == conn
+ end
+
+ test "as ip and ssl", %{name: name} do
+ url = "https://proxy-string.com"
+
+ :ok = Conn.open(url, name, proxy: {{127, 0, 0, 1}, 8123})
+ conn = Connections.checkin(url, name)
+
+ %Connections{
+ conns: %{
+ "https:proxy-string.com:443" => %Conn{
+ conn: ^conn,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+
+ reused_conn = Connections.checkin(url, name)
+
+ assert reused_conn == conn
+ end
+
+ test "as host and ssl", %{name: name} do
+ url = "https://proxy-tuple-atom.com"
+ :ok = Conn.open(url, name, proxy: {'localhost', 9050})
+ conn = Connections.checkin(url, name)
+
+ %Connections{
+ conns: %{
+ "https:proxy-tuple-atom.com:443" => %Conn{
+ conn: ^conn,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+
+ reused_conn = Connections.checkin(url, name)
+
+ assert reused_conn == conn
+ end
+
+ test "with socks type", %{name: name} do
+ url = "http://proxy-socks.com"
+
+ :ok = Conn.open(url, name, proxy: {:socks5, 'localhost', 1234})
+
+ conn = Connections.checkin(url, name)
+
+ %Connections{
+ conns: %{
+ "http:proxy-socks.com:80" => %Conn{
+ conn: ^conn,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+
+ reused_conn = Connections.checkin(url, name)
+
+ assert reused_conn == conn
+ end
+
+ test "with socks4 type and ssl", %{name: name} do
+ url = "https://proxy-socks.com"
+
+ :ok = Conn.open(url, name, proxy: {:socks4, 'localhost', 1234})
+
+ conn = Connections.checkin(url, name)
+
+ %Connections{
+ conns: %{
+ "https:proxy-socks.com:443" => %Conn{
+ conn: ^conn,
+ gun_state: :up
+ }
+ }
+ } = Connections.get_state(name)
+
+ reused_conn = Connections.checkin(url, name)
+
+ assert reused_conn == conn
+ end
+ end
+
+ describe "crf/3" do
+ setup do
+ crf = Connections.crf(1, 10, 1)
+ {:ok, crf: crf}
+ end
+
+ test "more used will have crf higher", %{crf: crf} do
+ # used 3 times
+ crf1 = Connections.crf(1, 10, crf)
+ crf1 = Connections.crf(1, 10, crf1)
+
+ # used 2 times
+ crf2 = Connections.crf(1, 10, crf)
+
+ assert crf1 > crf2
+ end
+
+ test "recently used will have crf higher on equal references", %{crf: crf} do
+ # used 3 sec ago
+ crf1 = Connections.crf(3, 10, crf)
+
+ # used 4 sec ago
+ crf2 = Connections.crf(4, 10, crf)
+
+ assert crf1 > crf2
+ end
+
+ test "equal crf on equal reference and time", %{crf: crf} do
+ # used 2 times
+ crf1 = Connections.crf(1, 10, crf)
+
+ # used 2 times
+ crf2 = Connections.crf(1, 10, crf)
+
+ assert crf1 == crf2
+ end
+
+ test "recently used will have higher crf", %{crf: crf} do
+ crf1 = Connections.crf(2, 10, crf)
+ crf1 = Connections.crf(1, 10, crf1)
+
+ crf2 = Connections.crf(3, 10, crf)
+ crf2 = Connections.crf(4, 10, crf2)
+ assert crf1 > crf2
+ end
+ end
+
+ describe "get_unused_conns/1" do
+ test "crf is equalent, sorting by reference", %{name: name} do
+ Connections.add_conn(name, "1", %Conn{
+ conn_state: :idle,
+ last_reference: now() - 1
+ })
+
+ Connections.add_conn(name, "2", %Conn{
+ conn_state: :idle,
+ last_reference: now()
+ })
+
+ assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name)
+ end
+
+ test "reference is equalent, sorting by crf", %{name: name} do
+ Connections.add_conn(name, "1", %Conn{
+ conn_state: :idle,
+ crf: 1.999
+ })
+
+ Connections.add_conn(name, "2", %Conn{
+ conn_state: :idle,
+ crf: 2
+ })
+
+ assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name)
+ end
+
+ test "higher crf and lower reference", %{name: name} do
+ Connections.add_conn(name, "1", %Conn{
+ conn_state: :idle,
+ crf: 3,
+ last_reference: now() - 1
+ })
+
+ Connections.add_conn(name, "2", %Conn{
+ conn_state: :idle,
+ crf: 2,
+ last_reference: now()
+ })
+
+ assert [{"2", _unused_conn} | _others] = Connections.get_unused_conns(name)
+ end
+
+ test "lower crf and lower reference", %{name: name} do
+ Connections.add_conn(name, "1", %Conn{
+ conn_state: :idle,
+ crf: 1.99,
+ last_reference: now() - 1
+ })
+
+ Connections.add_conn(name, "2", %Conn{
+ conn_state: :idle,
+ crf: 2,
+ last_reference: now()
+ })
+
+ assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name)
+ end
+ end
+
+ test "count/1", %{name: name} do
+ assert Connections.count(name) == 0
+ Connections.add_conn(name, "1", %Conn{conn: self()})
+ assert Connections.count(name) == 1
+ Connections.remove_conn(name, "1")
+ assert Connections.count(name) == 0
+ end
+
+ defp now do
+ :os.system_time(:second)
+ end
+end
diff --git a/test/reverse_proxy/client/tesla_test.exs b/test/reverse_proxy/client/tesla_test.exs
new file mode 100644
index 000000000..231271b0d
--- /dev/null
+++ b/test/reverse_proxy/client/tesla_test.exs
@@ -0,0 +1,93 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ReverseProxy.Client.TeslaTest do
+ use ExUnit.Case
+ use Pleroma.Tests.Helpers
+ alias Pleroma.ReverseProxy.Client
+ @moduletag :integration
+
+ clear_config_all(Pleroma.Gun.API) do
+ Pleroma.Config.put(Pleroma.Gun.API, Pleroma.Gun)
+ end
+
+ setup do
+ Application.put_env(:tesla, :adapter, Tesla.Adapter.Gun)
+
+ on_exit(fn ->
+ Application.put_env(:tesla, :adapter, Tesla.Mock)
+ end)
+ end
+
+ test "get response body stream" do
+ {:ok, status, headers, ref} =
+ Client.Tesla.request(
+ :get,
+ "http://httpbin.org/stream-bytes/10",
+ [{"accept", "application/octet-stream"}],
+ "",
+ []
+ )
+
+ assert status == 200
+ assert headers != []
+
+ {:ok, response, ref} = Client.Tesla.stream_body(ref)
+ check_ref(ref)
+ assert is_binary(response)
+ assert byte_size(response) == 10
+
+ assert :done == Client.Tesla.stream_body(ref)
+ assert :ok = Client.Tesla.close(ref)
+ end
+
+ test "head response" do
+ {:ok, status, headers} = Client.Tesla.request(:head, "https://httpbin.org/get", [], "")
+
+ assert status == 200
+ assert headers != []
+ end
+
+ test "get error response" do
+ {:ok, status, headers, _body} =
+ Client.Tesla.request(
+ :get,
+ "https://httpbin.org/status/500",
+ [],
+ ""
+ )
+
+ assert status == 500
+ assert headers != []
+ end
+
+ describe "client error" do
+ setup do
+ adapter = Application.get_env(:tesla, :adapter)
+ Application.put_env(:tesla, :adapter, Tesla.Adapter.Hackney)
+
+ on_exit(fn -> Application.put_env(:tesla, :adapter, adapter) end)
+ :ok
+ end
+
+ test "adapter doesn't support reading body in chunks" do
+ assert_raise RuntimeError,
+ "Elixir.Tesla.Adapter.Hackney doesn't support reading body in chunks",
+ fn ->
+ Client.Tesla.request(
+ :get,
+ "http://httpbin.org/stream-bytes/10",
+ [{"accept", "application/octet-stream"}],
+ ""
+ )
+ end
+ end
+ end
+
+ defp check_ref(%{pid: pid, stream: stream} = ref) do
+ assert is_pid(pid)
+ assert is_reference(stream)
+ assert ref[:fin]
+ end
+end
diff --git a/test/reverse_proxy_test.exs b/test/reverse_proxy/reverse_proxy_test.exs
index 0672f57db..f61fc02c5 100644
--- a/test/reverse_proxy_test.exs
+++ b/test/reverse_proxy/reverse_proxy_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReverseProxyTest do
- use Pleroma.Web.ConnCase, async: true
+ use Pleroma.Web.ConnCase
import ExUnit.CaptureLog
import Mox
alias Pleroma.ReverseProxy
@@ -29,11 +29,11 @@ defmodule Pleroma.ReverseProxyTest do
{"content-length", byte_size(json) |> to_string()}
], %{url: url}}
end)
- |> expect(:stream_body, invokes, fn %{url: url} ->
+ |> expect(:stream_body, invokes, fn %{url: url} = client ->
case Registry.lookup(Pleroma.ReverseProxy.ClientMock, url) do
[{_, 0}] ->
Registry.update_value(Pleroma.ReverseProxy.ClientMock, url, &(&1 + 1))
- {:ok, json}
+ {:ok, json, client}
[{_, 1}] ->
Registry.unregister(Pleroma.ReverseProxy.ClientMock, url)
@@ -78,7 +78,39 @@ defmodule Pleroma.ReverseProxyTest do
assert conn.halted
end
- describe "max_body " do
+ defp stream_mock(invokes, with_close? \\ false) do
+ ClientMock
+ |> expect(:request, fn :get, "/stream-bytes/" <> length, _, _, _ ->
+ Registry.register(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length, 0)
+
+ {:ok, 200, [{"content-type", "application/octet-stream"}],
+ %{url: "/stream-bytes/" <> length}}
+ end)
+ |> expect(:stream_body, invokes, fn %{url: "/stream-bytes/" <> length} = client ->
+ max = String.to_integer(length)
+
+ case Registry.lookup(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length) do
+ [{_, current}] when current < max ->
+ Registry.update_value(
+ Pleroma.ReverseProxy.ClientMock,
+ "/stream-bytes/" <> length,
+ &(&1 + 10)
+ )
+
+ {:ok, "0123456789", client}
+
+ [{_, ^max}] ->
+ Registry.unregister(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length)
+ :done
+ end
+ end)
+
+ if with_close? do
+ expect(ClientMock, :close, fn _ -> :ok end)
+ end
+ end
+
+ describe "max_body" do
test "length returns error if content-length more than option", %{conn: conn} do
user_agent_mock("hackney/1.15.1", 0)
@@ -94,38 +126,6 @@ defmodule Pleroma.ReverseProxyTest do
end) == ""
end
- defp stream_mock(invokes, with_close? \\ false) do
- ClientMock
- |> expect(:request, fn :get, "/stream-bytes/" <> length, _, _, _ ->
- Registry.register(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length, 0)
-
- {:ok, 200, [{"content-type", "application/octet-stream"}],
- %{url: "/stream-bytes/" <> length}}
- end)
- |> expect(:stream_body, invokes, fn %{url: "/stream-bytes/" <> length} ->
- max = String.to_integer(length)
-
- case Registry.lookup(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length) do
- [{_, current}] when current < max ->
- Registry.update_value(
- Pleroma.ReverseProxy.ClientMock,
- "/stream-bytes/" <> length,
- &(&1 + 10)
- )
-
- {:ok, "0123456789"}
-
- [{_, ^max}] ->
- Registry.unregister(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length)
- :done
- end
- end)
-
- if with_close? do
- expect(ClientMock, :close, fn _ -> :ok end)
- end
- end
-
test "max_body_length returns error if streaming body more than that option", %{conn: conn} do
stream_mock(3, true)
@@ -223,12 +223,12 @@ defmodule Pleroma.ReverseProxyTest do
Registry.register(Pleroma.ReverseProxy.ClientMock, "/headers", 0)
{:ok, 200, [{"content-type", "application/json"}], %{url: "/headers", headers: headers}}
end)
- |> expect(:stream_body, 2, fn %{url: url, headers: headers} ->
+ |> expect(:stream_body, 2, fn %{url: url, headers: headers} = client ->
case Registry.lookup(Pleroma.ReverseProxy.ClientMock, url) do
[{_, 0}] ->
Registry.update_value(Pleroma.ReverseProxy.ClientMock, url, &(&1 + 1))
headers = for {k, v} <- headers, into: %{}, do: {String.capitalize(k), v}
- {:ok, Jason.encode!(%{headers: headers})}
+ {:ok, Jason.encode!(%{headers: headers}), client}
[{_, 1}] ->
Registry.unregister(Pleroma.ReverseProxy.ClientMock, url)
@@ -305,11 +305,11 @@ defmodule Pleroma.ReverseProxyTest do
{:ok, 200, headers, %{url: "/disposition"}}
end)
- |> expect(:stream_body, 2, fn %{url: "/disposition"} ->
+ |> expect(:stream_body, 2, fn %{url: "/disposition"} = client ->
case Registry.lookup(Pleroma.ReverseProxy.ClientMock, "/disposition") do
[{_, 0}] ->
Registry.update_value(Pleroma.ReverseProxy.ClientMock, "/disposition", &(&1 + 1))
- {:ok, ""}
+ {:ok, "", client}
[{_, 1}] ->
Registry.unregister(Pleroma.ReverseProxy.ClientMock, "/disposition")
@@ -341,4 +341,45 @@ defmodule Pleroma.ReverseProxyTest do
assert {"content-disposition", "attachment; filename=\"filename.jpg\""} in conn.resp_headers
end
end
+
+ describe "tesla client using gun integration" do
+ @describetag :integration
+
+ clear_config(Pleroma.ReverseProxy.Client) do
+ Pleroma.Config.put(Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.Client.Tesla)
+ end
+
+ clear_config(Pleroma.Gun.API) do
+ Pleroma.Config.put(Pleroma.Gun.API, Pleroma.Gun)
+ end
+
+ setup do
+ adapter = Application.get_env(:tesla, :adapter)
+ Application.put_env(:tesla, :adapter, Tesla.Adapter.Gun)
+
+ on_exit(fn ->
+ Application.put_env(:tesla, :adapter, adapter)
+ end)
+ end
+
+ test "common", %{conn: conn} do
+ conn = ReverseProxy.call(conn, "http://httpbin.org/stream-bytes/10")
+ assert byte_size(conn.resp_body) == 10
+ assert conn.state == :chunked
+ assert conn.status == 200
+ end
+
+ test "ssl", %{conn: conn} do
+ conn = ReverseProxy.call(conn, "https://httpbin.org/stream-bytes/10")
+ assert byte_size(conn.resp_body) == 10
+ assert conn.state == :chunked
+ assert conn.status == 200
+ end
+
+ test "follow redirects", %{conn: conn} do
+ conn = ReverseProxy.call(conn, "https://httpbin.org/redirect/5")
+ assert conn.state == :chunked
+ assert conn.status == 200
+ end
+ end
end
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index ba3341327..5727871ea 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -107,7 +107,7 @@ defmodule HttpRequestMock do
"https://osada.macgirvin.com/.well-known/webfinger?resource=acct:mike@osada.macgirvin.com",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -120,7 +120,7 @@ defmodule HttpRequestMock do
"https://social.heldscal.la/.well-known/webfinger?resource=https://social.heldscal.la/user/29191",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -141,7 +141,7 @@ defmodule HttpRequestMock do
"https://pawoo.net/.well-known/webfinger?resource=acct:https://pawoo.net/users/pekorino",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -167,7 +167,7 @@ defmodule HttpRequestMock do
"https://social.stopwatchingus-heidelberg.de/.well-known/webfinger?resource=acct:https://social.stopwatchingus-heidelberg.de/user/18330",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -188,7 +188,7 @@ defmodule HttpRequestMock do
"https://mamot.fr/.well-known/webfinger?resource=acct:https://mamot.fr/users/Skruyb",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -201,7 +201,7 @@ defmodule HttpRequestMock do
"https://social.heldscal.la/.well-known/webfinger?resource=nonexistant@social.heldscal.la",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -214,7 +214,7 @@ defmodule HttpRequestMock do
"https://squeet.me/xrd/?uri=lain@squeet.me",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -227,7 +227,7 @@ defmodule HttpRequestMock do
"https://mst3k.interlinked.me/users/luciferMysticus",
_,
_,
- Accept: "application/activity+json"
+ [{"accept", "application/activity+json"}]
) do
{:ok,
%Tesla.Env{
@@ -248,7 +248,7 @@ defmodule HttpRequestMock do
"https://hubzilla.example.org/channel/kaniini",
_,
_,
- Accept: "application/activity+json"
+ [{"accept", "application/activity+json"}]
) do
{:ok,
%Tesla.Env{
@@ -257,7 +257,7 @@ defmodule HttpRequestMock do
}}
end
- def get("https://niu.moe/users/rye", _, _, Accept: "application/activity+json") do
+ def get("https://niu.moe/users/rye", _, _, [{"accept", "application/activity+json"}]) do
{:ok,
%Tesla.Env{
status: 200,
@@ -265,7 +265,7 @@ defmodule HttpRequestMock do
}}
end
- def get("https://n1u.moe/users/rye", _, _, Accept: "application/activity+json") do
+ def get("https://n1u.moe/users/rye", _, _, [{"accept", "application/activity+json"}]) do
{:ok,
%Tesla.Env{
status: 200,
@@ -284,7 +284,7 @@ defmodule HttpRequestMock do
}}
end
- def get("https://puckipedia.com/", _, _, Accept: "application/activity+json") do
+ def get("https://puckipedia.com/", _, _, [{"accept", "application/activity+json"}]) do
{:ok,
%Tesla.Env{
status: 200,
@@ -308,9 +308,9 @@ defmodule HttpRequestMock do
}}
end
- def get("https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39", _, _,
- Accept: "application/activity+json"
- ) do
+ def get("https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39", _, _, [
+ {"accept", "application/activity+json"}
+ ]) do
{:ok,
%Tesla.Env{
status: 200,
@@ -318,7 +318,7 @@ defmodule HttpRequestMock do
}}
end
- def get("https://mobilizon.org/@tcit", _, _, Accept: "application/activity+json") do
+ def get("https://mobilizon.org/@tcit", _, _, [{"accept", "application/activity+json"}]) do
{:ok,
%Tesla.Env{
status: 200,
@@ -358,7 +358,7 @@ defmodule HttpRequestMock do
}}
end
- def get("http://mastodon.example.org/users/admin", _, _, Accept: "application/activity+json") do
+ def get("http://mastodon.example.org/users/admin", _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
@@ -366,7 +366,9 @@ defmodule HttpRequestMock do
}}
end
- def get("http://mastodon.example.org/users/relay", _, _, Accept: "application/activity+json") do
+ def get("http://mastodon.example.org/users/relay", _, _, [
+ {"accept", "application/activity+json"}
+ ]) do
{:ok,
%Tesla.Env{
status: 200,
@@ -374,7 +376,9 @@ defmodule HttpRequestMock do
}}
end
- def get("http://mastodon.example.org/users/gargron", _, _, Accept: "application/activity+json") do
+ def get("http://mastodon.example.org/users/gargron", _, _, [
+ {"accept", "application/activity+json"}
+ ]) do
{:error, :nxdomain}
end
@@ -557,7 +561,7 @@ defmodule HttpRequestMock do
"http://mastodon.example.org/@admin/99541947525187367",
_,
_,
- Accept: "application/activity+json"
+ _
) do
{:ok,
%Tesla.Env{
@@ -582,7 +586,7 @@ defmodule HttpRequestMock do
}}
end
- def get("https://mstdn.io/users/mayuutann", _, _, Accept: "application/activity+json") do
+ def get("https://mstdn.io/users/mayuutann", _, _, [{"accept", "application/activity+json"}]) do
{:ok,
%Tesla.Env{
status: 200,
@@ -594,7 +598,7 @@ defmodule HttpRequestMock do
"https://mstdn.io/users/mayuutann/statuses/99568293732299394",
_,
_,
- Accept: "application/activity+json"
+ [{"accept", "application/activity+json"}]
) do
{:ok,
%Tesla.Env{
@@ -614,7 +618,7 @@ defmodule HttpRequestMock do
}}
end
- def get(url, _, _, Accept: "application/xrd+xml,application/jrd+json")
+ def get(url, _, _, [{"accept", "application/xrd+xml,application/jrd+json"}])
when url in [
"https://pleroma.soykaf.com/.well-known/webfinger?resource=acct:https://pleroma.soykaf.com/users/lain",
"https://pleroma.soykaf.com/.well-known/webfinger?resource=https://pleroma.soykaf.com/users/lain"
@@ -641,7 +645,7 @@ defmodule HttpRequestMock do
"https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/1",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -685,7 +689,7 @@ defmodule HttpRequestMock do
"https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/5381",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -738,7 +742,7 @@ defmodule HttpRequestMock do
"https://social.sakamoto.gq/.well-known/webfinger?resource=https://social.sakamoto.gq/users/eal",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -751,7 +755,7 @@ defmodule HttpRequestMock do
"https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056",
_,
_,
- Accept: "application/atom+xml"
+ [{"accept", "application/atom+xml"}]
) do
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/sakamoto.atom")}}
end
@@ -768,7 +772,7 @@ defmodule HttpRequestMock do
"https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/lambadalambda",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -790,7 +794,7 @@ defmodule HttpRequestMock do
"http://gs.example.org/.well-known/webfinger?resource=http://gs.example.org:4040/index.php/user/1",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -804,7 +808,7 @@ defmodule HttpRequestMock do
"http://gs.example.org:4040/index.php/user/1",
_,
_,
- Accept: "application/activity+json"
+ [{"accept", "application/activity+json"}]
) do
{:ok, %Tesla.Env{status: 406, body: ""}}
end
@@ -840,7 +844,7 @@ defmodule HttpRequestMock do
"https://squeet.me/xrd?uri=lain@squeet.me",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -853,7 +857,7 @@ defmodule HttpRequestMock do
"https://social.heldscal.la/.well-known/webfinger?resource=shp@social.heldscal.la",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -866,7 +870,7 @@ defmodule HttpRequestMock do
"https://social.heldscal.la/.well-known/webfinger?resource=invalid_content@social.heldscal.la",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok, %Tesla.Env{status: 200, body: ""}}
end
@@ -883,7 +887,7 @@ defmodule HttpRequestMock do
"http://framatube.org/main/xrd?uri=framasoft@framatube.org",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -905,7 +909,7 @@ defmodule HttpRequestMock do
"http://gnusocial.de/main/xrd?uri=winterdienst@gnusocial.de",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -942,7 +946,7 @@ defmodule HttpRequestMock do
"https://gerzilla.de/xrd/?uri=kaniini@gerzilla.de",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -1005,7 +1009,7 @@ defmodule HttpRequestMock do
%Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/osada-user-indio.json")}}
end
- def get("https://social.heldscal.la/user/23211", _, _, Accept: "application/activity+json") do
+ def get("https://social.heldscal.la/user/23211", _, _, [{"accept", "application/activity+json"}]) do
{:ok, Tesla.Mock.json(%{"id" => "https://social.heldscal.la/user/23211"}, status: 200)}
end
@@ -1138,7 +1142,7 @@ defmodule HttpRequestMock do
"https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=lain@zetsubou.xn--q9jyb4c",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -1151,7 +1155,7 @@ defmodule HttpRequestMock do
"https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=https://zetsubou.xn--q9jyb4c/users/lain",
_,
_,
- Accept: "application/xrd+xml,application/jrd+json"
+ [{"accept", "application/xrd+xml,application/jrd+json"}]
) do
{:ok,
%Tesla.Env{
@@ -1173,7 +1177,9 @@ defmodule HttpRequestMock do
}}
end
- def get("https://info.pleroma.site/activity.json", _, _, Accept: "application/activity+json") do
+ def get("https://info.pleroma.site/activity.json", _, _, [
+ {"accept", "application/activity+json"}
+ ]) do
{:ok,
%Tesla.Env{
status: 200,
@@ -1185,7 +1191,9 @@ defmodule HttpRequestMock do
{:ok, %Tesla.Env{status: 404, body: ""}}
end
- def get("https://info.pleroma.site/activity2.json", _, _, Accept: "application/activity+json") do
+ def get("https://info.pleroma.site/activity2.json", _, _, [
+ {"accept", "application/activity+json"}
+ ]) do
{:ok,
%Tesla.Env{
status: 200,
@@ -1197,7 +1205,9 @@ defmodule HttpRequestMock do
{:ok, %Tesla.Env{status: 404, body: ""}}
end
- def get("https://info.pleroma.site/activity3.json", _, _, Accept: "application/activity+json") do
+ def get("https://info.pleroma.site/activity3.json", _, _, [
+ {"accept", "application/activity+json"}
+ ]) do
{:ok,
%Tesla.Env{
status: 200,
diff --git a/test/user_invite_token_test.exs b/test/user_invite_token_test.exs
index 111e40361..671560e41 100644
--- a/test/user_invite_token_test.exs
+++ b/test/user_invite_token_test.exs
@@ -4,7 +4,6 @@
defmodule Pleroma.UserInviteTokenTest do
use ExUnit.Case, async: true
- use Pleroma.DataCase
alias Pleroma.UserInviteToken
describe "valid_invite?/1 one time invites" do
@@ -64,7 +63,6 @@ defmodule Pleroma.UserInviteTokenTest do
test "expires yesterday returns false", %{invite: invite} do
invite = %{invite | expires_at: Date.add(Date.utc_today(), -1)}
- invite = Repo.insert!(invite)
refute UserInviteToken.valid_invite?(invite)
end
end
@@ -82,7 +80,6 @@ defmodule Pleroma.UserInviteTokenTest do
test "overdue date and less uses returns false", %{invite: invite} do
invite = %{invite | expires_at: Date.add(Date.utc_today(), -1)}
- invite = Repo.insert!(invite)
refute UserInviteToken.valid_invite?(invite)
end
@@ -93,7 +90,6 @@ defmodule Pleroma.UserInviteTokenTest do
test "overdue date with more uses returns false", %{invite: invite} do
invite = %{invite | expires_at: Date.add(Date.utc_today(), -1), uses: 5}
- invite = Repo.insert!(invite)
refute UserInviteToken.valid_invite?(invite)
end
end
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index 777e85938..7add75263 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -2513,7 +2513,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"value" => "Tesla.Adapter.Httpc",
"db" => [":adapter"]
}
- ]
+ ],
+ "need_reboot" => true
}
end
@@ -2600,7 +2601,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
%{"tuple" => [":seconds_valid", 60]},
%{"tuple" => [":path", ""]},
%{"tuple" => [":key1", nil]},
- %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
%{"tuple" => [":regex1", "~r/https:\/\/example.com/"]},
%{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]},
%{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]},
@@ -2630,7 +2630,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
%{"tuple" => [":seconds_valid", 60]},
%{"tuple" => [":path", ""]},
%{"tuple" => [":key1", nil]},
- %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
%{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]},
%{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]},
%{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]},
@@ -2643,7 +2642,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
":seconds_valid",
":path",
":key1",
- ":partial_chain",
":regex1",
":regex2",
":regex3",
@@ -2657,7 +2655,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"value" => "Tesla.Adapter.Httpc",
"db" => [":adapter"]
}
- ]
+ ],
+ "need_reboot" => true
}
end
diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs
index 848300ef3..759501a67 100644
--- a/test/web/common_api/common_api_utils_test.exs
+++ b/test/web/common_api/common_api_utils_test.exs
@@ -474,6 +474,13 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
activity = insert(:note_activity, user: user, note: object)
Pleroma.Repo.delete(object)
+ obj_url = activity.data["object"]
+
+ Tesla.Mock.mock(fn
+ %{method: :get, url: ^obj_url} ->
+ %Tesla.Env{status: 404, body: ""}
+ end)
+
assert Utils.maybe_notify_mentioned_recipients(["test-test"], activity) == [
"test-test"
]
diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs
index 089d55577..9f931c941 100644
--- a/test/web/push/impl_test.exs
+++ b/test/web/push/impl_test.exs
@@ -134,7 +134,7 @@ defmodule Pleroma.Web.Push.ImplTest do
user = insert(:user, nickname: "Bob")
other_user = insert(:user)
{:ok, _, _, activity} = CommonAPI.follow(user, other_user)
- object = Object.normalize(activity)
+ object = Object.normalize(activity, false)
assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has followed you"