adding gun adapter
This commit is contained in:
parent
962eb8d4ac
commit
514c899275
63 changed files with 3615 additions and 389 deletions
|
@ -73,6 +73,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Support for custom Elixir modules (such as MRF policies)
|
||||
- User settings: Add _This account is a_ option.
|
||||
- OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`).
|
||||
- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires OTP version older that 22.2, otherwise pleroma won’t start. For hackney OTP update is not required.
|
||||
<details>
|
||||
<summary>API Changes</summary>
|
||||
|
||||
|
|
|
@ -58,20 +58,6 @@
|
|||
|
||||
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 +171,12 @@
|
|||
}
|
||||
|
||||
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",
|
||||
|
@ -612,6 +590,49 @@
|
|||
|
||||
config :pleroma, configurable_from_database: false
|
||||
|
||||
config :pleroma, :connections_pool,
|
||||
receive_connection_timeout: 250,
|
||||
max_connections: 250,
|
||||
retry: 5,
|
||||
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.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
@ -2728,7 +2728,7 @@
|
|||
key: :adapter,
|
||||
type: :module,
|
||||
description: "Tesla adapter",
|
||||
suggestions: [Tesla.Adapter.Hackney]
|
||||
suggestions: [Tesla.Adapter.Hackney, Tesla.Adapter.Gun]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -94,6 +94,8 @@
|
|||
|
||||
config :pleroma, :modules, runtime_dir: "test/fixtures/modules"
|
||||
|
||||
config :pleroma, Pleroma.Gun.API, Pleroma.Gun.API.Mock
|
||||
|
||||
if File.exists?("./config/test.secret.exs") do
|
||||
import_config "test.secret.exs"
|
||||
else
|
||||
|
|
|
@ -731,6 +731,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`
|
||||
|
|
|
@ -368,8 +368,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
|
||||
|
||||
|
@ -388,6 +387,39 @@ 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.
|
||||
|
||||
* `: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
|
||||
|
|
|
@ -74,4 +74,43 @@ def run(["render_timeline", nickname | _] = args) do
|
|||
inputs: inputs
|
||||
)
|
||||
end
|
||||
|
||||
def run(["adapters"]) do
|
||||
start_pleroma()
|
||||
|
||||
:ok =
|
||||
Pleroma.Pool.Connections.open_conn(
|
||||
"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
|
||||
|
|
|
@ -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 @@ def run(["ls-packs" | args]) 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 @@ def run(["get-packs" | args]) 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}]: "))
|
||||
|
|
|
@ -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 @@ def named_version, do: @name <> " " <> @version
|
|||
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 @@ def user_agent 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 @@ def start(_type, _args) 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 @@ def start(_type, _args) 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 @@ def start(_type, _args) 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 @@ defp setup_instrumenters 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 @@ defp idempotency_expiration,
|
|||
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 @@ defp build_cachex(type, opts),
|
|||
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 @@ defp chat_child(_env, true) 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 @@ defp task_children(_) 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
|
||||
|
|
|
@ -278,8 +278,6 @@ defp do_convert({:proxy_url, {type, host, port}}) 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 @@ defp do_transform(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}
|
|||
{: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
|
||||
|
|
|
@ -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 @@ def load_and_update_env(deleted \\ [], restart_pleroma? \\ true) 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 @@ defp merge_and_update(setting) 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 =
|
||||
|
|
26
lib/pleroma/gun/api.ex
Normal file
26
lib/pleroma/gun/api.ex
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Gun.API do
|
||||
@callback open(charlist(), pos_integer(), map()) :: {:ok, pid()}
|
||||
@callback info(pid()) :: map()
|
||||
@callback close(pid()) :: :ok
|
||||
@callback await_up(pid) :: {:ok, atom()} | {:error, atom()}
|
||||
@callback connect(pid(), map()) :: reference()
|
||||
@callback await(pid(), reference()) :: {:response, :fin, 200, []}
|
||||
|
||||
def open(host, port, opts), do: api().open(host, port, opts)
|
||||
|
||||
def info(pid), do: api().info(pid)
|
||||
|
||||
def close(pid), do: api().close(pid)
|
||||
|
||||
def await_up(pid), do: api().await_up(pid)
|
||||
|
||||
def connect(pid, opts), do: api().connect(pid, opts)
|
||||
|
||||
def await(pid, ref), do: api().await(pid, ref)
|
||||
|
||||
defp api, do: Pleroma.Config.get([Pleroma.Gun.API], Pleroma.Gun)
|
||||
end
|
151
lib/pleroma/gun/api/mock.ex
Normal file
151
lib/pleroma/gun/api/mock.ex
Normal file
|
@ -0,0 +1,151 @@
|
|||
# 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), do: {:ok, :http}
|
||||
|
||||
@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
|
29
lib/pleroma/gun/conn.ex
Normal file
29
lib/pleroma/gun/conn.ex
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Gun.Conn do
|
||||
@moduledoc """
|
||||
Struct for gun connection data
|
||||
"""
|
||||
@type gun_state :: :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
|
||||
end
|
45
lib/pleroma/gun/gun.ex
Normal file
45
lib/pleroma/gun/gun.ex
Normal file
|
@ -0,0 +1,45 @@
|
|||
# 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), 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
|
||||
end
|
64
lib/pleroma/http/adapter.ex
Normal file
64
lib/pleroma/http/adapter.ex
Normal file
|
@ -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
|
123
lib/pleroma/http/adapter/gun.ex
Normal file
123
lib/pleroma/http/adapter/gun.ex
Normal file
|
@ -0,0 +1,123 @@
|
|||
# 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: 20_000,
|
||||
domain_lookup_timeout: 5_000,
|
||||
tls_handshake_timeout: 5_000,
|
||||
retry_timeout: 100,
|
||||
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)]}
|
||||
]
|
||||
]
|
||||
|
||||
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.info(
|
||||
"Gun connections pool checkin was not succesfull. Trying to open conn for next request."
|
||||
)
|
||||
|
||||
:ok = Connections.open_conn(uri, :gun_connections, opts)
|
||||
opts
|
||||
|
||||
conn when is_pid(conn) ->
|
||||
Logger.debug("received conn #{inspect(conn)} #{Connections.compose_uri(uri)}")
|
||||
|
||||
opts
|
||||
|> Keyword.put(:conn, conn)
|
||||
|> Keyword.put(:close_conn, false)
|
||||
end
|
||||
rescue
|
||||
error ->
|
||||
Logger.warn("Gun connections pool checkin caused error #{inspect(error)}")
|
||||
opts
|
||||
catch
|
||||
:exit, {:timeout, _} ->
|
||||
Logger.info(
|
||||
"Gun connections pool checkin with timeout error #{Connections.compose_uri(uri)}"
|
||||
)
|
||||
|
||||
opts
|
||||
|
||||
:exit, error ->
|
||||
Logger.warn("Gun pool checkin exited with error #{inspect(error)}")
|
||||
opts
|
||||
end
|
||||
end
|
||||
end
|
41
lib/pleroma/http/adapter/hackney.ex
Normal file
41
lib/pleroma/http/adapter/hackney.ex
Normal file
|
@ -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
|
|
@ -4,40 +4,99 @@
|
|||
|
||||
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]
|
||||
|
||||
require Logger
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.HTTP.Adapter
|
||||
|
||||
@doc """
|
||||
Configure a client connection
|
||||
|
||||
# Returns
|
||||
|
||||
Tesla.Env.client
|
||||
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
|
||||
|
||||
# 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)
|
||||
defp pool_timeout(opts) do
|
||||
timeout =
|
||||
Config.get([:pools, opts[:pool], :timeout]) || Config.get([:pools, :default, :timeout])
|
||||
|
||||
@hackney_options
|
||||
|> Keyword.merge(adapter_options)
|
||||
|> Keyword.merge(options)
|
||||
|> Keyword.merge(proxy: proxy_url)
|
||||
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
|
||||
|
||||
@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
|
||||
|
|
|
@ -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,23 +52,78 @@ 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)
|
||||
: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}
|
||||
|
||||
params = Keyword.get(options, :params, [])
|
||||
:exit, e ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
%{}
|
||||
|> 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)).()
|
||||
@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}
|
||||
|
@ -52,35 +133,16 @@ def request(method, url, body \\ "", headers \\ [], options \\ []) do
|
|||
end
|
||||
end
|
||||
|
||||
defp process_sni_options(options, nil), do: options
|
||||
|
||||
defp process_sni_options(options, url) do
|
||||
uri = URI.parse(url)
|
||||
host = uri.host |> to_charlist()
|
||||
|
||||
case uri.scheme do
|
||||
"https" -> options ++ [ssl: [server_name_indication: host]]
|
||||
_ -> options
|
||||
end
|
||||
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
|
||||
|
||||
def process_request_options(options) do
|
||||
Keyword.merge(Pleroma.HTTP.Connection.hackney_options([]), options)
|
||||
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
|
||||
|
|
23
lib/pleroma/http/request.ex
Normal file
23
lib/pleroma/http/request.ex
Normal file
|
@ -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
|
|
@ -7,77 +7,54 @@ defmodule Pleroma.HTTP.RequestBuilder do
|
|||
Helper functions for building Tesla requests
|
||||
"""
|
||||
|
||||
alias Pleroma.HTTP.Request
|
||||
alias Tesla.Multipart
|
||||
|
||||
@doc """
|
||||
Specify the request method when building a request
|
||||
|
||||
## Parameters
|
||||
|
||||
- request (Map) - Collected request options
|
||||
- m (atom) - Request method
|
||||
|
||||
## Returns
|
||||
|
||||
Map
|
||||
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
|
||||
|
||||
## Parameters
|
||||
|
||||
- request (Map) - Collected request options
|
||||
- u (String) - Request URL
|
||||
|
||||
## Returns
|
||||
|
||||
Map
|
||||
"""
|
||||
@spec url(map(), String.t()) :: map()
|
||||
def url(request, u) do
|
||||
Map.put_new(request, :url, u)
|
||||
end
|
||||
@spec method(Request.t(), Request.method()) :: Request.t()
|
||||
def method(request, m), do: %{request | method: m}
|
||||
|
||||
@doc """
|
||||
Specify the request method when building a request
|
||||
"""
|
||||
@spec url(Request.t(), Request.url()) :: Request.t()
|
||||
def url(request, u), do: %{request | url: u}
|
||||
|
||||
@doc """
|
||||
Add headers to the request
|
||||
"""
|
||||
@spec headers(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 @@ def add_optional_params(request, definitions, [{key, value} | tail]) 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
|
||||
|
|
|
@ -137,7 +137,7 @@ defp make_signature(id, date) do
|
|||
date: date
|
||||
})
|
||||
|
||||
[{:Signature, signature}]
|
||||
[{"signature", signature}]
|
||||
end
|
||||
|
||||
defp sign_fetch(headers, id, date) do
|
||||
|
@ -150,7 +150,7 @@ defp sign_fetch(headers, id, date) 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
|
||||
|
@ -162,7 +162,7 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
|||
date = Pleroma.Signature.signed_date()
|
||||
|
||||
headers =
|
||||
[{:Accept, "application/activity+json"}]
|
||||
[{"accept", "application/activity+json"}]
|
||||
|> maybe_date_fetch(date)
|
||||
|> sign_fetch(id, date)
|
||||
|
||||
|
|
63
lib/pleroma/otp_version.ex
Normal file
63
lib/pleroma/otp_version.ex
Normal file
|
@ -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
|
415
lib/pleroma/pool/connections.ex
Normal file
415
lib/pleroma/pool/connections.ex
Normal file
|
@ -0,0 +1,415 @@
|
|||
# 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
|
||||
|
||||
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
|
||||
alias Pleroma.Gun.Conn
|
||||
|
||||
@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 = Pleroma.Config.get([:connections_pool, :receive_connection_timeout], 250)
|
||||
|
||||
GenServer.call(
|
||||
name,
|
||||
{:checkin, uri},
|
||||
timeout
|
||||
)
|
||||
end
|
||||
|
||||
@spec open_conn(String.t() | URI.t(), atom(), keyword()) :: :ok
|
||||
def open_conn(url, name, opts \\ [])
|
||||
def open_conn(url, name, opts) when is_binary(url), do: open_conn(URI.parse(url), name, opts)
|
||||
|
||||
def open_conn(%URI{} = uri, name, opts) do
|
||||
pool_opts = Pleroma.Config.get([:connections_pool], [])
|
||||
|
||||
opts =
|
||||
opts
|
||||
|> Enum.into(%{})
|
||||
|> Map.put_new(:receive, false)
|
||||
|> Map.put_new(:retry, pool_opts[:retry] || 5)
|
||||
|> Map.put_new(:retry_timeout, pool_opts[:retry_timeout] || 100)
|
||||
|> Map.put_new(:await_up_timeout, pool_opts[:await_up_timeout] || 5_000)
|
||||
|
||||
GenServer.cast(name, {:open_conn, %{opts: opts, uri: uri}})
|
||||
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 checkout(pid(), pid(), atom()) :: :ok
|
||||
def checkout(conn, pid, name) do
|
||||
GenServer.cast(name, {:checkout, conn, pid})
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:open_conn, %{opts: opts, uri: uri}}, state) do
|
||||
Logger.debug("opening new #{compose_uri(uri)}")
|
||||
max_connections = state.opts[:max_connections]
|
||||
|
||||
key = compose_key(uri)
|
||||
|
||||
if Enum.count(state.conns) < max_connections do
|
||||
open_conn(key, uri, state, opts)
|
||||
else
|
||||
try_to_open_conn(key, uri, state, opts)
|
||||
end
|
||||
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.warn("checkout for closed conn #{inspect(conn_pid)}")
|
||||
state
|
||||
|
||||
nil ->
|
||||
Logger.info("checkout for alive conn #{inspect(conn_pid)}, but is not in state")
|
||||
state
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:checkin, uri}, from, state) do
|
||||
Logger.debug("checkin #{compose_uri(uri)}")
|
||||
key = compose_key(uri)
|
||||
|
||||
case state.conns[key] do
|
||||
%{conn: conn, gun_state: gun_state} = current_conn when gun_state == :up ->
|
||||
Logger.debug("reusing conn #{compose_uri(uri)}")
|
||||
|
||||
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_info({:gun_up, conn_pid, _protocol}, state) do
|
||||
state =
|
||||
with true <- Process.alive?(conn_pid),
|
||||
conn_key when is_binary(conn_key) <- compose_key_gun_info(conn_pid),
|
||||
{key, conn} <- find_conn(state.conns, conn_pid, conn_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.warn(":gun.info caused error")
|
||||
state
|
||||
|
||||
false ->
|
||||
Logger.warn(":gun_up message for closed conn #{inspect(conn_pid)}")
|
||||
state
|
||||
|
||||
nil ->
|
||||
Logger.warn(
|
||||
":gun_up message for alive conn #{inspect(conn_pid)}, but deleted from 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
|
||||
# we can't get info on this pid, because pid is dead
|
||||
state =
|
||||
with true <- Process.alive?(conn_pid),
|
||||
{key, conn} <- find_conn(state.conns, conn_pid) do
|
||||
if conn.retries == 5 do
|
||||
Logger.debug("closing conn if retries is eq 5 #{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 ->
|
||||
# gun can send gun_down for closed conn, maybe connection is not closed yet
|
||||
Logger.warn(":gun_down message for closed conn #{inspect(conn_pid)}")
|
||||
state
|
||||
|
||||
nil ->
|
||||
Logger.warn(
|
||||
":gun_down message for alive conn #{inspect(conn_pid)}, but deleted from state"
|
||||
)
|
||||
|
||||
:ok = API.close(conn_pid)
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
defp compose_key(%URI{scheme: scheme, host: host, port: port}), do: "#{scheme}:#{host}:#{port}"
|
||||
|
||||
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
|
||||
|
||||
defp open_conn(key, uri, state, %{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),
|
||||
stream <- API.connect(conn, connect_opts),
|
||||
{:response, :fin, 200, _} <- API.await(conn, stream),
|
||||
state <-
|
||||
put_in(state.conns[key], %Conn{
|
||||
conn: conn,
|
||||
gun_state: :up,
|
||||
conn_state: :active,
|
||||
last_reference: :os.system_time(:second)
|
||||
}) do
|
||||
{:noreply, state}
|
||||
else
|
||||
error ->
|
||||
Logger.warn(
|
||||
"Received error on opening connection with http proxy #{uri.scheme}://#{
|
||||
compose_uri(uri)
|
||||
}: #{inspect(error)}"
|
||||
)
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp open_conn(key, uri, state, %{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),
|
||||
state <-
|
||||
put_in(state.conns[key], %Conn{
|
||||
conn: conn,
|
||||
gun_state: :up,
|
||||
conn_state: :active,
|
||||
last_reference: :os.system_time(:second)
|
||||
}) do
|
||||
{:noreply, state}
|
||||
else
|
||||
error ->
|
||||
Logger.warn(
|
||||
"Received error on opening connection with socks proxy #{uri.scheme}://#{
|
||||
compose_uri(uri)
|
||||
}: #{inspect(error)}"
|
||||
)
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp open_conn(key, %URI{host: host, port: port} = uri, state, opts) do
|
||||
Logger.debug("opening conn #{compose_uri(uri)}")
|
||||
{_type, host} = Pleroma.HTTP.Adapter.domain_or_ip(host)
|
||||
|
||||
with {:ok, conn} <- API.open(host, port, opts),
|
||||
{:ok, _} <- API.await_up(conn),
|
||||
state <-
|
||||
put_in(state.conns[key], %Conn{
|
||||
conn: conn,
|
||||
gun_state: :up,
|
||||
conn_state: :active,
|
||||
last_reference: :os.system_time(:second)
|
||||
}) do
|
||||
Logger.debug("new conn opened #{compose_uri(uri)}")
|
||||
Logger.debug("replying to the call #{compose_uri(uri)}")
|
||||
{:noreply, state}
|
||||
else
|
||||
error ->
|
||||
Logger.warn(
|
||||
"Received error on opening connection #{uri.scheme}://#{compose_uri(uri)}: #{
|
||||
inspect(error)
|
||||
}"
|
||||
)
|
||||
|
||||
{:noreply, state}
|
||||
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
|
||||
|
||||
@spec get_unused_conns(map()) :: [{domain(), conn()}]
|
||||
def get_unused_conns(conns) do
|
||||
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)
|
||||
end
|
||||
|
||||
defp try_to_open_conn(key, uri, state, opts) do
|
||||
Logger.debug("try to open conn #{compose_uri(uri)}")
|
||||
|
||||
with [{close_key, least_used} | _conns] <- get_unused_conns(state.conns),
|
||||
:ok <- API.close(least_used.conn),
|
||||
state <-
|
||||
put_in(
|
||||
state.conns,
|
||||
Map.delete(state.conns, close_key)
|
||||
) do
|
||||
Logger.debug(
|
||||
"least used conn found and closed #{inspect(least_used.conn)} #{compose_uri(uri)}"
|
||||
)
|
||||
|
||||
open_conn(key, uri, state, opts)
|
||||
else
|
||||
[] -> {:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def crf(current, steps, crf) do
|
||||
1 + :math.pow(0.5, current / steps) * crf
|
||||
end
|
||||
|
||||
def compose_uri(%URI{} = uri), do: "#{uri.host}#{uri.path}"
|
||||
end
|
22
lib/pleroma/pool/pool.ex
Normal file
22
lib/pleroma/pool/pool.ex
Normal file
|
@ -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
|
72
lib/pleroma/pool/request.ex
Normal file
72
lib/pleroma/pool/request.ex
Normal file
|
@ -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
|
36
lib/pleroma/pool/supervisor.ex
Normal file
36
lib/pleroma/pool/supervisor.ex
Normal file
|
@ -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
|
|
@ -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 @@ def stream_body(ref), do: client().stream_body(ref)
|
|||
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
|
||||
|
|
24
lib/pleroma/reverse_proxy/client/hackney.ex
Normal file
24
lib/pleroma/reverse_proxy/client/hackney.ex
Normal file
|
@ -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
|
87
lib/pleroma/reverse_proxy/client/tesla.ex
Normal file
87
lib/pleroma/reverse_proxy/client/tesla.ex
Normal file
|
@ -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
|
|
@ -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 @@ def call(conn = %{method: method}, url, opts) when method in @methods 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 @@ def call(conn, _, _) 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 @@ defp chunk_reply(conn, client, opts, sent_so_far, duration) 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 <-
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 @@ def parse(url) when is_binary(url) 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),
|
||||
data <-
|
||||
Floki.attribute(html, "link[rel~=me]", "href") ++
|
||||
Floki.attribute(html, "a[rel~=me]", "href") do
|
||||
|
|
|
@ -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 @@ defp get_ttl_from_image(data, url) 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
|
||||
|
|
|
@ -205,7 +205,7 @@ def finger(account) 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)
|
||||
|
|
4
mix.exs
4
mix.exs
|
@ -120,6 +120,10 @@ defp deps do
|
|||
{:cachex, "~> 3.0.2"},
|
||||
{:poison, "~> 3.0", override: true},
|
||||
{:tesla, "~> 1.3", 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"},
|
||||
|
|
2
mix.lock
2
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"},
|
||||
|
@ -45,6 +46,7 @@
|
|||
"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", "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", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
|
||||
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
|
|
|
@ -83,7 +83,7 @@ test "converts tags to hash tags", %{activity: %{object: %{data: data} = object}
|
|||
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])
|
||||
|
|
|
@ -478,14 +478,6 @@ test "simple keyword" 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([
|
||||
|
|
1
test/fixtures/warnings/otp_version/21.1
vendored
Normal file
1
test/fixtures/warnings/otp_version/21.1
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
21.1
|
1
test/fixtures/warnings/otp_version/22.1
vendored
Normal file
1
test/fixtures/warnings/otp_version/22.1
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
22.1
|
1
test/fixtures/warnings/otp_version/22.4
vendored
Normal file
1
test/fixtures/warnings/otp_version/22.4
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
22.4
|
1
test/fixtures/warnings/otp_version/23.0
vendored
Normal file
1
test/fixtures/warnings/otp_version/23.0
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
23.0
|
1
test/fixtures/warnings/otp_version/error
vendored
Normal file
1
test/fixtures/warnings/otp_version/error
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
22
|
1
test/fixtures/warnings/otp_version/undefined
vendored
Normal file
1
test/fixtures/warnings/otp_version/undefined
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
undefined
|
33
test/gun/gun_test.exs
Normal file
33
test/gun/gun_test.exs
Normal file
|
@ -0,0 +1,33 @@
|
|||
# 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)
|
||||
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
|
266
test/http/adapter/gun_test.exs
Normal file
266
test/http/adapter/gun_test.exs
Normal file
|
@ -0,0 +1,266 @@
|
|||
# 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.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 = Connections.open_conn(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 = Connections.open_conn(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: :info)
|
||||
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 succesfull. 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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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
|
54
test/http/adapter/hackney_test.exs
Normal file
54
test/http/adapter/hackney_test.exs
Normal file
|
@ -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
|
65
test/http/adapter_test.exs
Normal file
65
test/http/adapter_test.exs
Normal file
|
@ -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
|
142
test/http/connection_test.exs
Normal file
142
test/http/connection_test.exs
Normal file
|
@ -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.Pool.Connections.open_conn(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
|
|
@ -5,30 +5,32 @@
|
|||
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
|
||||
clear_config([:http, :send_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
|
||||
|
@ -40,19 +42,19 @@ test "don't add if keyword is empty" do
|
|||
|
||||
test "add query parameter" do
|
||||
assert RequestBuilder.add_optional_params(
|
||||
%{},
|
||||
%Request{},
|
||||
%{query: :query, body: :body, another: :val},
|
||||
[
|
||||
{:query, "param1=val1¶m2=val2"},
|
||||
{:body, "some body"}
|
||||
]
|
||||
) == %{query: "param1=val1¶m2=val2", body: "some body"}
|
||||
) == %Request{query: "param1=val1¶m2=val2", body: "some body"}
|
||||
end
|
||||
end
|
||||
|
||||
describe "add_param/4" do
|
||||
test "add file parameter" do
|
||||
%{
|
||||
%Request{
|
||||
body: %Tesla.Multipart{
|
||||
boundary: _,
|
||||
content_type_params: [],
|
||||
|
@ -69,7 +71,7 @@ test "add file parameter" 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
|
||||
|
@ -81,7 +83,7 @@ test "add key to body" do
|
|||
%Tesla.Multipart.Part{
|
||||
body: "\"someval\"",
|
||||
dispositions: [name: "somekey"],
|
||||
headers: ["Content-Type": "application/json"]
|
||||
headers: [{"content-type", "application/json"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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 @@ test "returns successfully result" 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 @@ test "returns successfully result for json content-type" 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
|
||||
|
|
|
@ -649,6 +649,13 @@ test "notifications are deleted if a remote user is deleted" 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()
|
||||
|
||||
|
|
58
test/otp_version_test.exs
Normal file
58
test/otp_version_test.exs
Normal file
|
@ -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
|
959
test/pool/connections_test.exs
Normal file
959
test/pool/connections_test.exs
Normal file
|
@ -0,0 +1,959 @@
|
|||
# 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
|
||||
|
||||
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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(http_url, name)
|
||||
conn = Connections.checkin(http_url, name)
|
||||
assert is_pid(conn)
|
||||
assert Process.alive?(conn)
|
||||
|
||||
refute Connections.checkin(https_url, name)
|
||||
:ok = Connections.open_conn(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 ->
|
||||
:ok = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(https_url, name)
|
||||
:ok = Connections.open_conn(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 = Connections.open_conn(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 reuse it on next request", %{name: name} do
|
||||
url = "http://httpbin.org"
|
||||
:ok = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(https1, name)
|
||||
:ok = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(https1, name)
|
||||
:ok = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(https1, name)
|
||||
:ok = Connections.open_conn(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 = Connections.open_conn(https1, name)
|
||||
:ok = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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 = Connections.open_conn(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" do
|
||||
conns = %{
|
||||
"1" => %Conn{
|
||||
conn_state: :idle,
|
||||
last_reference: now() - 1
|
||||
},
|
||||
"2" => %Conn{
|
||||
conn_state: :idle,
|
||||
last_reference: now()
|
||||
}
|
||||
}
|
||||
|
||||
assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns)
|
||||
end
|
||||
|
||||
test "reference is equalent, sorting by crf" do
|
||||
conns = %{
|
||||
"1" => %Conn{
|
||||
conn_state: :idle,
|
||||
crf: 1.999
|
||||
},
|
||||
"2" => %Conn{
|
||||
conn_state: :idle,
|
||||
crf: 2
|
||||
}
|
||||
}
|
||||
|
||||
assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns)
|
||||
end
|
||||
|
||||
test "higher crf and lower reference" do
|
||||
conns = %{
|
||||
"1" => %Conn{
|
||||
conn_state: :idle,
|
||||
crf: 3,
|
||||
last_reference: now() - 1
|
||||
},
|
||||
"2" => %Conn{
|
||||
conn_state: :idle,
|
||||
crf: 2,
|
||||
last_reference: now()
|
||||
}
|
||||
}
|
||||
|
||||
assert [{"2", _unused_conn} | _others] = Connections.get_unused_conns(conns)
|
||||
end
|
||||
|
||||
test "lower crf and lower reference" do
|
||||
conns = %{
|
||||
"1" => %Conn{
|
||||
conn_state: :idle,
|
||||
crf: 1.99,
|
||||
last_reference: now() - 1
|
||||
},
|
||||
"2" => %Conn{
|
||||
conn_state: :idle,
|
||||
crf: 2,
|
||||
last_reference: now()
|
||||
}
|
||||
}
|
||||
|
||||
assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns)
|
||||
end
|
||||
end
|
||||
|
||||
defp now do
|
||||
:os.system_time(:second)
|
||||
end
|
||||
end
|
93
test/reverse_proxy/client/tesla_test.exs
Normal file
93
test/reverse_proxy/client/tesla_test.exs
Normal file
|
@ -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
|
|
@ -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 @@ defp user_agent_mock(user_agent, invokes) 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 @@ test "closed connection", %{conn: conn} 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 @@ test "length returns error if content-length more than option", %{conn: conn} 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 @@ defp headers_mock(_) 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 @@ defp disposition_headers_mock(headers) 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 @@ test "with content-disposition header", %{conn: conn} 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
|
|
@ -107,7 +107,7 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"https://mst3k.interlinked.me/users/luciferMysticus",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/activity+json"
|
||||
[{"accept", "application/activity+json"}]
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
|
@ -248,7 +248,7 @@ def get(
|
|||
"https://hubzilla.example.org/channel/kaniini",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/activity+json"
|
||||
[{"accept", "application/activity+json"}]
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
|
@ -257,7 +257,7 @@ def get(
|
|||
}}
|
||||
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 @@ def get("https://niu.moe/users/rye", _, _, Accept: "application/activity+json")
|
|||
}}
|
||||
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 @@ def get("http://mastodon.example.org/users/admin/statuses/100787282858396771", _
|
|||
}}
|
||||
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 @@ def get("https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
|
|||
}}
|
||||
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 @@ def get("https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39", _,
|
|||
}}
|
||||
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 @@ def get("https://wedistribute.org/wp-json/pterotype/v1/actor/-blog", _, _, _) 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 @@ def get("http://mastodon.example.org/users/admin", _, _, Accept: "application/ac
|
|||
}}
|
||||
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 @@ def get("http://mastodon.example.org/users/relay", _, _, Accept: "application/ac
|
|||
}}
|
||||
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 @@ def get(
|
|||
"http://mastodon.example.org/@admin/99541947525187367",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/activity+json"
|
||||
_
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
|
@ -582,7 +586,7 @@ def get("https://shitposter.club/notice/7369654", _, _, _) 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 @@ def get(
|
|||
"https://mstdn.io/users/mayuutann/statuses/99568293732299394",
|
||||
_,
|
||||
_,
|
||||
Accept: "application/activity+json"
|
||||
[{"accept", "application/activity+json"}]
|
||||
) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
|
@ -614,7 +618,7 @@ def get("https://pleroma.soykaf.com/users/lain/feed.atom", _, _, _) 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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get("https://apfed.club/channel/indio", _, _, _) 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 @@ def get(
|
|||
"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 @@ def get(
|
|||
"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 @@ def get(
|
|||
}}
|
||||
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 @@ def get("https://info.pleroma.site/activity.json", _, _, _) 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 @@ def get("https://info.pleroma.site/activity2.json", _, _, _) 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,
|
||||
|
|
|
@ -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 @@ test "expires today returns true", %{invite: invite} 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 @@ test "not overdue date and less uses returns true", %{invite: invite} 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 @@ test "not overdue date with more uses returns false", %{invite: invite} 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
|
||||
|
|
|
@ -2439,7 +2439,8 @@ test "saving full setting if value is not keyword", %{conn: conn} do
|
|||
"value" => "Tesla.Adapter.Httpc",
|
||||
"db" => [":adapter"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"need_reboot" => true
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -2526,7 +2527,6 @@ test "common config example", %{conn: conn} 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"]},
|
||||
|
@ -2556,7 +2556,6 @@ test "common config example", %{conn: conn} 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"]},
|
||||
|
@ -2569,7 +2568,6 @@ test "common config example", %{conn: conn} do
|
|||
":seconds_valid",
|
||||
":path",
|
||||
":key1",
|
||||
":partial_chain",
|
||||
":regex1",
|
||||
":regex2",
|
||||
":regex3",
|
||||
|
@ -2583,7 +2581,8 @@ test "common config example", %{conn: conn} do
|
|||
"value" => "Tesla.Adapter.Httpc",
|
||||
"db" => [":adapter"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"need_reboot" => true
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -474,6 +474,13 @@ test "returns recipients when object not found" 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"
|
||||
]
|
||||
|
|
|
@ -126,7 +126,7 @@ test "renders title and body for follow activity" 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"
|
||||
|
||||
|
|
Loading…
Reference in a new issue