move to 2.5.5
[anni] / lib / pleroma / http.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.HTTP do
6   @moduledoc """
7     Wrapper for `Tesla.request/2`.
8   """
9
10   alias Pleroma.HTTP.AdapterHelper
11   alias Pleroma.HTTP.Request
12   alias Pleroma.HTTP.RequestBuilder, as: Builder
13   alias Tesla.Client
14   alias Tesla.Env
15
16   require Logger
17
18   @type t :: __MODULE__
19   @type method() :: :get | :post | :put | :delete | :head
20
21   @doc """
22   Performs GET request.
23
24   See `Pleroma.HTTP.request/5`
25   """
26   @spec get(Request.url() | nil, Request.headers(), keyword()) ::
27           nil | {:ok, Env.t()} | {:error, any()}
28   def get(url, headers \\ [], options \\ [])
29   def get(nil, _, _), do: nil
30   def get(url, headers, options), do: request(:get, url, "", headers, options)
31
32   @spec head(Request.url(), Request.headers(), keyword()) :: {:ok, Env.t()} | {:error, any()}
33   def head(url, headers \\ [], options \\ []), do: request(:head, url, "", headers, options)
34
35   @doc """
36   Performs POST request.
37
38   See `Pleroma.HTTP.request/5`
39   """
40   @spec post(Request.url(), String.t(), Request.headers(), keyword()) ::
41           {:ok, Env.t()} | {:error, any()}
42   def post(url, body, headers \\ [], options \\ []),
43     do: request(:post, url, body, headers, options)
44
45   @doc """
46   Builds and performs http request.
47
48   # Arguments:
49   `method` - :get, :post, :put, :delete, :head
50   `url` - full url
51   `body` - request body
52   `headers` - a keyworld list of headers, e.g. `[{"content-type", "text/plain"}]`
53   `options` - custom, per-request middleware or adapter options
54
55   # Returns:
56   `{:ok, %Tesla.Env{}}` or `{:error, error}`
57
58   """
59   @spec request(method(), Request.url(), String.t(), Request.headers(), keyword()) ::
60           {:ok, Env.t()} | {:error, any()}
61   def request(method, url, body, headers, options) when is_binary(url) do
62     uri = URI.parse(url)
63     adapter_opts = AdapterHelper.options(uri, options || [])
64
65     options = put_in(options[:adapter], adapter_opts)
66     params = options[:params] || []
67     request = build_request(method, headers, options, url, body, params)
68
69     adapter = Application.get_env(:tesla, :adapter)
70
71     client = Tesla.client(adapter_middlewares(adapter), adapter)
72
73     maybe_limit(
74       fn ->
75         request(client, request)
76       end,
77       adapter,
78       adapter_opts
79     )
80   end
81
82   @spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()}
83   def request(client, request), do: Tesla.request(client, request)
84
85   defp build_request(method, headers, options, url, body, params) do
86     Builder.new()
87     |> Builder.method(method)
88     |> Builder.headers(headers)
89     |> Builder.opts(options)
90     |> Builder.url(url)
91     |> Builder.add_param(:body, :body, body)
92     |> Builder.add_param(:query, :query, params)
93     |> Builder.convert_to_keyword()
94   end
95
96   @prefix Pleroma.Gun.ConnectionPool
97   defp maybe_limit(fun, Tesla.Adapter.Gun, opts) do
98     ConcurrentLimiter.limit(:"#{@prefix}.#{opts[:pool] || :default}", fun)
99   end
100
101   defp maybe_limit(fun, _, _) do
102     fun.()
103   end
104
105   defp adapter_middlewares(Tesla.Adapter.Gun) do
106     [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool]
107   end
108
109   defp adapter_middlewares(Tesla.Adapter.Hackney) do
110     if Pleroma.Config.get([:http, :adapter, :follow_redirect]) != false do
111       [Tesla.Middleware.FollowRedirects]
112     else
113       []
114     end
115   end
116
117   defp adapter_middlewares(_) do
118     if Pleroma.Config.get(:env) == :test do
119       # Emulate redirects in test env, which are handled by adapters in other environments
120       [Tesla.Middleware.FollowRedirects]
121     else
122       []
123     end
124   end
125 end