Repository: wojtekmach/req
Pull request #380 (2024-06-27 10:44:07)

Extract private Req.Finch


Patch:
diff --git a/lib/req.ex b/lib/req.ex
index ba36548..b970095 100644
--- a/lib/req.ex
+++ b/lib/req.ex
@@ -135,6 +135,8 @@ defmodule Req do
   @req Req.Request.new()
        |> Req.Steps.attach()
 
+  @default_finch_options Req.Finch.pool_options(%{})
+
   @doc """
   Returns a new request struct with built-in steps.
 
@@ -361,7 +363,8 @@ defmodule Req do
 
         * `:timeout` - socket connect timeout in milliseconds, defaults to `30_000`.
 
-        * `:protocols` - the HTTP protocols to use, defaults to `#{inspect(Req.Application.__default_protocols__())}`.
+        * `:protocols` - the HTTP protocols to use, defaults to
+          `#{inspect(Keyword.fetch!(@default_finch_options, :protocols))}`.
 
         * `:hostname` - Mint explicit hostname.
 
diff --git a/lib/req/application.ex b/lib/req/application.ex
index e742faf..00da0f8 100644
--- a/lib/req/application.ex
+++ b/lib/req/application.ex
@@ -9,9 +9,7 @@ defmodule Req.Application do
       {Finch,
        name: Req.Finch,
        pools: %{
-         default: [
-           protocols: __default_protocols__()
-         ]
+         default: Req.Finch.pool_options(%{})
        }},
       {DynamicSupervisor, strategy: :one_for_one, name: Req.FinchSupervisor},
       {Req.Test.Ownership, name: Req.Test.Ownership}
@@ -19,8 +17,4 @@ defmodule Req.Application do
 
     Supervisor.start_link(children, strategy: :one_for_one)
   end
-
-  def __default_protocols__ do
-    [:http1]
-  end
 end
diff --git a/lib/req/finch.ex b/lib/req/finch.ex
new file mode 100644
index 0000000..c7fc07e
--- /dev/null
+++ b/lib/req/finch.ex
@@ -0,0 +1,397 @@
+defmodule Req.Finch do
+  @moduledoc false
+
+  @doc """
+  Runs the request using `Finch`.
+  """
+  def run(request) do
+    # URI.parse removes `[` and `]` so we can't check for these. The host
+    # should not have `:` so it should be safe to check for it.
+    request =
+      if !request.options[:inet6] and
+           (request.url.host || "") =~ ":" do
+        request = put_in(request.options[:inet6], true)
+        # ...and have to put them back for host header.
+        Req.Request.put_new_header(request, "host", "[#{request.url.host}]")
+      else
+        request
+      end
+
+    finch_name = finch_name(request)
+
+    request_headers =
+      if unquote(Req.MixProject.legacy_headers_as_lists?()) do
+        request.headers
+      else
+        for {name, values} <- request.headers,
+            value <- values do
+          {name, value}
+        end
+      end
+
+    body =
+      case request.body do
+        iodata when is_binary(iodata) or is_list(iodata) ->
+          iodata
+
+        nil ->
+          nil
+
+        enumerable ->
+          {:stream, enumerable}
+      end
+
+    finch_request =
+      Finch.build(request.method, request.url, request_headers, body)
+      |> Map.replace!(:unix_socket, request.options[:unix_socket])
+      |> add_private_options(request.options[:finch_private])
+
+    finch_options =
+      request.options |> Map.take([:receive_timeout, :pool_timeout]) |> Enum.to_list()
+
+    run(request, finch_request, finch_name, finch_options)
+  end
+
+  defp run(req, finch_req, finch_name, finch_options) do
+    case req.options[:finch_request] do
+      fun when is_function(fun, 4) ->
+        fun.(req, finch_req, finch_name, finch_options)
+
+      deprecated_fun when is_function(deprecated_fun, 1) ->
+        IO.warn(
+          "passing a :finch_request function accepting a single argument is deprecated. " <>
+            "See Req.Steps.run_finch/1 for more information."
+        )
+
+        {req, run_finch_request(deprecated_fun.(finch_req), finch_name, finch_options)}
+
+      nil ->
+        case req.into do
+          nil ->
+            {req, run_finch_request(finch_req, finch_name, finch_options)}
+
+          fun when is_function(fun, 2) ->
+            finch_stream_into_fun(req, finch_req, finch_name, finch_options, fun)
+
+          :legacy_self ->
+            finch_stream_into_legacy_self(req, finch_req, finch_name, finch_options)
+
+          :self ->
+            finch_stream_into_self(req, finch_req, finch_name, finch_options)
+
+          collectable ->
+            finch_stream_into_collectable(req, finch_req, finch_name, finch_options, collectable)
+        end
+    end
+  end
+
+  defp finch_stream_into_fun(req, finch_req, finch_name, finch_options, fun) do
+    resp = Req.Response.new()
+
+    fun = fn
+      {:status, status}, {req, resp} ->
+        {:cont, {req, %{resp | status: status}}}
+
+      {:headers, fields}, {req, resp} ->
+        fields = finch_fields_to_map(fields)
+        resp = update_in(resp.headers, &Map.merge(&1, fields))
+        {:cont, {req, resp}}
+
+      {:data, data}, acc ->
+        fun.({:data, data}, acc)
+
+      {:trailers, fields}, {req, resp} ->
+        fields = finch_fields_to_map(fields)
+        resp = update_in(resp.trailers, &Map.merge(&1, fields))
+        {:cont, {req, resp}}
+    end
+
+    case Finch.stream_while(finch_req, finch_name, {req, resp}, fun, finch_options) do
+      {:ok, acc} ->
+        acc
+
+      {:error, %Mint.TransportError{reason: reason}} ->
+        {req, %Req.TransportError{reason: reason}}
+
+      {:error, %Mint.HTTPError{module: Mint.HTTP1, reason: reason}} ->
+        {req, %Req.HTTPError{protocol: :http1, reason: reason}}
+
+      {:error, %Mint.HTTPError{module: Mint.HTTP2, reason: reason}} ->
+        {req, %Req.HTTPError{protocol: :http2, reason: reason}}
+
+      {:error, %Finch.Error{reason: reason}} ->
+        {req, %Req.HTTPError{protocol: :http2, reason: reason}}
+
+      {:error, exception} ->
+        {req, exception}
+    end
+  end
+
+  defp finch_stream_into_collectable(req, finch_req, finch_name, finch_options, collectable) do
+    {acc, collector} = Collectable.into(collectable)
+    resp = Req.Response.new()
+
+    fun = fn
+      {:status, status}, {acc, req, resp} ->
+        {acc, req, %{resp | status: status}}
+
+      {:headers, fields}, {acc, req, resp} ->
+        fields = finch_fields_to_map(fields)
+        resp = update_in(resp.headers, &Map.merge(&1, fields))
+        {acc, req, resp}
+
+      {:data, data}, {acc, req, resp} ->
+        acc = collector.(acc, {:cont, data})
+        {acc, req, resp}
+
+      {:trailers, fields}, {acc, req, resp} ->
+        fields = finch_fields_to_map(fields)
+        resp = update_in(resp.trailers, &Map.merge(&1, fields))
+        {acc, req, resp}
+    end
+
+    case Finch.stream(finch_req, finch_name, {acc, req, resp}, fun, finch_options) do
+      {:ok, {acc, req, resp}} ->
+        acc = collector.(acc, :done)
+        {req, %{resp | body: acc}}
+
+      {:error, %Mint.TransportError{reason: reason}} ->
+        {req, %Req.TransportError{reason: reason}}
+
+      {:error, %Mint.HTTPError{module: Mint.HTTP1, reason: reason}} ->
+        {req, %Req.HTTPError{protocol: :http1, reason: reason}}
+
+      {:error, %Mint.HTTPError{module: Mint.HTTP2, reason: reason}} ->
+        {req, %Req.HTTPError{protocol: :http2, reason: reason}}
+
+      {:error, %Finch.Error{reason: reason}} ->
+        {req, %Req.HTTPError{protocol: :http2, reason: reason}}
+
+      {:error, exception} ->
+        {req, exception}
+    end
+  end
+
+  defp finch_stream_into_legacy_self(req, finch_req, finch_name, finch_options) do
+    ref = Finch.async_request(finch_req, finch_name, finch_options)
+
+    {:status, status} =
+      receive do
+        {^ref, message} ->
+          message
+      end
+
+    headers =
+      receive do
+        {^ref, message} ->
+          {:headers, headers} = message
+
+          Enum.reduce(headers, %{}, fn {name, value}, acc ->
+            Map.update(acc, name, [value], &(&1 ++ [value]))
+          end)
+      end
+
+    async = %Req.Response.Async{
+      pid: self(),
+      ref: ref,
+      stream_fun: &finch_parse_message/2,
+      cancel_fun: &finch_cancel/1
+    }
+
+    req = put_in(req.async, async)
+    resp = Req.Response.new(status: status, headers: headers)
+    {req, resp}
+  end
+
+  defp finch_stream_into_self(req, finch_req, finch_name, finch_options) do
+    ref = Finch.async_request(finch_req, finch_name, finch_options)
+
+    {:status, status} =
+      receive do
+        {^ref, message} ->
+          message
+      end
+
+    headers =
+      receive do
+        {^ref, message} ->
+          # TODO: handle trailers
+          {:headers, headers} = message
+
+          Enum.reduce(headers, %{}, fn {name, value}, acc ->
+            Map.update(acc, name, [value], &(&1 ++ [value]))
+          end)
+      end
+
+    async = %Req.Response.Async{
+      pid: self(),
+      ref: ref,
+      stream_fun: &finch_parse_message/2,
+      cancel_fun: &finch_cancel/1
+    }
+
+    resp = Req.Response.new(status: status, headers: headers)
+    resp = put_in(resp.body, async)
+    {req, resp}
+  end
+
+  defp run_finch_request(finch_request, finch_name, finch_options) do
+    case Finch.request(finch_request, finch_name, finch_options) do
+      {:ok, response} ->
+        Req.Response.new(response)
+
+      {:error, %Mint.TransportError{reason: reason}} ->
+        %Req.TransportError{reason: reason}
+
+      {:error, %Mint.HTTPError{module: Mint.HTTP1, reason: reason}} ->
+        %Req.HTTPError{protocol: :http1, reason: reason}
+
+      {:error, %Mint.HTTPError{module: Mint.HTTP2, reason: reason}} ->
+        %Req.HTTPError{protocol: :http2, reason: reason}
+
+      {:error, %Finch.Error{reason: reason}} ->
+        %Req.HTTPError{protocol: :http2, reason: reason}
+
+      {:error, exception} ->
+        exception
+    end
+  end
+
+  defp add_private_options(finch_request, nil) do
+    finch_request
+  end
+
+  defp add_private_options(finch_request, private_options)
+       when is_list(private_options) or is_map(private_options) do
+    Enum.reduce(private_options, finch_request, fn {k, v}, acc_finch_req ->
+      Finch.Request.put_private(acc_finch_req, k, v)
+    end)
+  end
+
+  defp finch_fields_to_map(fields) do
+    Enum.reduce(fields, %{}, fn {name, value}, acc ->
+      Map.update(acc, name, [value], &(&1 ++ [value]))
+    end)
+  end
+
+  defp finch_parse_message(ref, {ref, {:data, data}}) do
+    {:ok, [data: data]}
+  end
+
+  defp finch_parse_message(ref, {ref, :done}) do
+    {:ok, [:done]}
+  end
+
+  defp finch_parse_message(ref, {ref, {:trailers, trailers}}) do
+    {:ok, [trailers: trailers]}
+  end
+
+  defp finch_parse_message(ref, {ref, {:error, reason}}) do
+    {:error, reason}
+  end
+
+  defp finch_cancel(ref) do
+    Finch.cancel_async_request(ref)
+  end
+
+  defp finch_name(request) do
+    custom_options? =
+      Map.has_key?(request.options, :connect_options) or Map.has_key?(request.options, :inet6)
+
+    cond do
+      request.options[:finch] && custom_options? ->
+        raise ArgumentError, "cannot set both :finch and :connect_options"
+
+      custom_options? ->
+        pool_options = pool_options(request.options)
+
+        name =
+          pool_options
+          |> :erlang.term_to_binary()
+          |> :erlang.md5()
+          |> Base.url_encode64(padding: false)
+
+        name = Module.concat(Req.FinchSupervisor, "Pool_#{name}")
+
+        case DynamicSupervisor.start_child(
+               Req.FinchSupervisor,
+               {Finch, name: name, pools: %{default: pool_options}}
+             ) do
+          {:ok, _} ->
+            name
+
+          {:error, {:already_started, _}} ->
+            name
+        end
+
+      true ->
+        Req.Finch
+    end
+  end
+
+  @doc """
+  Returns Finch pool options for the given Req `options`.
+  """
+  def pool_options(options) when is_map(options) do
+    connect_options = options[:connect_options] || []
+    inet6_options = options |> Map.take([:inet6]) |> Enum.to_list()
+
+    Req.Request.validate_options(
+      connect_options,
+      MapSet.new([
+        :timeout,
+        :protocols,
+        :transport_opts,
+        :proxy_headers,
+        :proxy,
+        :client_settings,
+        :hostname,
+
+        # TODO: Remove on Req v1.0
+        :protocol
+      ])
+    )
+
+    transport_opts =
+      Keyword.merge(
+        Keyword.take(connect_options, [:timeout]) ++ inet6_options,
+        Keyword.get(connect_options, :transport_opts, [])
+      )
+
+    conn_opts =
+      Keyword.take(connect_options, [:hostname, :proxy, :proxy_headers, :client_settings]) ++
+        if transport_opts != [] do
+          [transport_opts: transport_opts]
+        else
+          []
+        end
+
+    protocols =
+      cond do
+        protocols = connect_options[:protocols] ->
+          protocols
+
+        protocol = connect_options[:protocol] ->
+          IO.warn([
+            "setting `connect_options: [protocol: protocol]` is deprecated, ",
+            "use `connect_options: [protocols: protocols]` instead"
+          ])
+
+          [protocol]
+
+        true ->
+          [:http1]
+      end
+
+    [protocols: protocols] ++
+      if conn_opts != [] do
+        [conn_opts: conn_opts]
+      else
+        []
+      end
+  end
+
+  def pool_options(options) when is_list(options) do
+    pool_options(Req.new(options).options)
+  end
+end
diff --git a/lib/req/steps.ex b/lib/req/steps.ex
index 0243f6c..a458183 100644
--- a/lib/req/steps.ex
+++ b/lib/req/steps.ex
@@ -630,6 +630,8 @@ defmodule Req.Steps do
     end
   end
 
+  @default_finch_options Req.Finch.pool_options(%{})
+
   @doc """
   Runs the request using `Finch`.
 
@@ -669,7 +671,8 @@ defmodule Req.Steps do
 
         * `:timeout` - socket connect timeout in milliseconds, defaults to `30_000`.
 
-        * `:protocols` - the HTTP protocols to use, defaults to `#{inspect(Req.Application.__default_protocols__())}`.
+        * `:protocols` - the HTTP protocols to use, defaults to
+          `#{inspect(Keyword.fetch!(@default_finch_options, :protocols))}`.
 
         * `:hostname` - Mint explicit hostname, see `Mint.HTTP.connect/4` for more information.
 
@@ -743,384 +746,7 @@ defmodule Req.Steps do
   """
   @doc step: :request
   def run_finch(request) do
-    # URI.parse removes `[` and `]` so we can't check for these. The host
-    # should not have `:` so it should be safe to check for it.
-    request =
-      if !request.options[:inet6] and
-           (request.url.host || "") =~ ":" do
-        request = put_in(request.options[:inet6], true)
-        # ...and have to put them back for host header.
-        Req.Request.put_new_header(request, "host", "[#{request.url.host}]")
-      else
-        request
-      end
-
-    finch_name = finch_name(request)
-
-    request_headers =
-      if unquote(Req.MixProject.legacy_headers_as_lists?()) do
-        request.headers
-      else
-        for {name, values} <- request.headers,
-            value <- values do
-          {name, value}
-        end
-      end
-
-    body =
-      case request.body do
-        iodata when is_binary(iodata) or is_list(iodata) ->
-          iodata
-
-        nil ->
-          nil
-
-        enumerable ->
-          {:stream, enumerable}
-      end
-
-    finch_request =
-      Finch.build(request.method, request.url, request_headers, body)
-      |> Map.replace!(:unix_socket, request.options[:unix_socket])
-      |> add_private_options(request.options[:finch_private])
-
-    finch_options =
-      request.options |> Map.take([:receive_timeout, :pool_timeout]) |> Enum.to_list()
-
-    run_finch(request, finch_request, finch_name, finch_options)
-  end
-
-  defp run_finch(req, finch_req, finch_name, finch_options) do
-    case req.options[:finch_request] do
-      fun when is_function(fun, 4) ->
-        fun.(req, finch_req, finch_name, finch_options)
-
-      deprecated_fun when is_function(deprecated_fun, 1) ->
-        IO.warn(
-          "passing a :finch_request function accepting a single argument is deprecated. " <>
-            "See Req.Steps.run_finch/1 for more information."
-        )
-
-        {req, run_finch_request(deprecated_fun.(finch_req), finch_name, finch_options)}
-
-      nil ->
-        case req.into do
-          nil ->
-            {req, run_finch_request(finch_req, finch_name, finch_options)}
-
-          fun when is_function(fun, 2) ->
-            finch_stream_into_fun(req, finch_req, finch_name, finch_options, fun)
-
-          :legacy_self ->
-            finch_stream_into_legacy_self(req, finch_req, finch_name, finch_options)
-
-          :self ->
-            finch_stream_into_self(req, finch_req, finch_name, finch_options)
-
-          collectable ->
-            finch_stream_into_collectable(req, finch_req, finch_name, finch_options, collectable)
-        end
-    end
-  end
-
-  defp finch_stream_into_fun(req, finch_req, finch_name, finch_options, fun) do
-    resp = Req.Response.new()
-
-    fun = fn
-      {:status, status}, {req, resp} ->
-        {:cont, {req, %{resp | status: status}}}
-
-      {:headers, fields}, {req, resp} ->
-        fields = finch_fields_to_map(fields)
-        resp = update_in(resp.headers, &Map.merge(&1, fields))
-        {:cont, {req, resp}}
-
-      {:data, data}, acc ->
-        fun.({:data, data}, acc)
-
-      {:trailers, fields}, {req, resp} ->
-        fields = finch_fields_to_map(fields)
-        resp = update_in(resp.trailers, &Map.merge(&1, fields))
-        {:cont, {req, resp}}
-    end
-
-    case Finch.stream_while(finch_req, finch_name, {req, resp}, fun, finch_options) do
-      {:ok, acc} ->
-        acc
-
-      {:error, %Mint.TransportError{reason: reason}} ->
-        {req, %Req.TransportError{reason: reason}}
-
-      {:error, %Mint.HTTPError{module: Mint.HTTP1, reason: reason}} ->
-        {req, %Req.HTTPError{protocol: :http1, reason: reason}}
-
-      {:error, %Mint.HTTPError{module: Mint.HTTP2, reason: reason}} ->
-        {req, %Req.HTTPError{protocol: :http2, reason: reason}}
-
-      {:error, %Finch.Error{reason: reason}} ->
-        {req, %Req.HTTPError{protocol: :http2, reason: reason}}
-
-      {:error, exception} ->
-        {req, exception}
-    end
-  end
-
-  defp finch_stream_into_collectable(req, finch_req, finch_name, finch_options, collectable) do
-    {acc, collector} = Collectable.into(collectable)
-    resp = Req.Response.new()
-
-    fun = fn
-      {:status, status}, {acc, req, resp} ->
-        {acc, req, %{resp | status: status}}
-
-      {:headers, fields}, {acc, req, resp} ->
-        fields = finch_fields_to_map(fields)
-        resp = update_in(resp.headers, &Map.merge(&1, fields))
-        {acc, req, resp}
-
-      {:data, data}, {acc, req, resp} ->
-        acc = collector.(acc, {:cont, data})
-        {acc, req, resp}
-
-      {:trailers, fields}, {acc, req, resp} ->
-        fields = finch_fields_to_map(fields)
-        resp = update_in(resp.trailers, &Map.merge(&1, fields))
-        {acc, req, resp}
-    end
-
-    case Finch.stream(finch_req, finch_name, {acc, req, resp}, fun, finch_options) do
-      {:ok, {acc, req, resp}} ->
-        acc = collector.(acc, :done)
-        {req, %{resp | body: acc}}
-
-      {:error, %Mint.TransportError{reason: reason}} ->
-        {req, %Req.TransportError{reason: reason}}
-
-      {:error, %Mint.HTTPError{module: Mint.HTTP1, reason: reason}} ->
-        {req, %Req.HTTPError{protocol: :http1, reason: reason}}
-
-      {:error, %Mint.HTTPError{module: Mint.HTTP2, reason: reason}} ->
-        {req, %Req.HTTPError{protocol: :http2, reason: reason}}
-
-      {:error, %Finch.Error{reason: reason}} ->
-        {req, %Req.HTTPError{protocol: :http2, reason: reason}}
-
-      {:error, exception} ->
-        {req, exception}
-    end
-  end
-
-  defp finch_stream_into_legacy_self(req, finch_req, finch_name, finch_options) do
-    ref = Finch.async_request(finch_req, finch_name, finch_options)
-
-    {:status, status} =
-      receive do
-        {^ref, message} ->
-          message
-      end
-
-    headers =
-      receive do
-        {^ref, message} ->
-          {:headers, headers} = message
-
-          Enum.reduce(headers, %{}, fn {name, value}, acc ->
-            Map.update(acc, name, [value], &(&1 ++ [value]))
-          end)
-      end
-
-    async = %Req.Response.Async{
-      pid: self(),
-      ref: ref,
-      stream_fun: &finch_parse_message/2,
-      cancel_fun: &finch_cancel/1
-    }
-
-    req = put_in(req.async, async)
-    resp = Req.Response.new(status: status, headers: headers)
-    {req, resp}
-  end
-
-  defp finch_stream_into_self(req, finch_req, finch_name, finch_options) do
-    ref = Finch.async_request(finch_req, finch_name, finch_options)
-
-    {:status, status} =
-      receive do
-        {^ref, message} ->
-          message
-      end
-
-    headers =
-      receive do
-        {^ref, message} ->
-          # TODO: handle trailers
-          {:headers, headers} = message
-
-          Enum.reduce(headers, %{}, fn {name, value}, acc ->
-            Map.update(acc, name, [value], &(&1 ++ [value]))
-          end)
-      end
-
-    async = %Req.Response.Async{
-      pid: self(),
-      ref: ref,
-      stream_fun: &finch_parse_message/2,
-      cancel_fun: &finch_cancel/1
-    }
-
-    resp = Req.Response.new(status: status, headers: headers)
-    resp = put_in(resp.body, async)
-    {req, resp}
-  end
-
-  defp run_finch_request(finch_request, finch_name, finch_options) do
-    case Finch.request(finch_request, finch_name, finch_options) do
-      {:ok, response} ->
-        Req.Response.new(response)
-
-      {:error, %Mint.TransportError{reason: reason}} ->
-        %Req.TransportError{reason: reason}
-
-      {:error, %Mint.HTTPError{module: Mint.HTTP1, reason: reason}} ->
-        %Req.HTTPError{protocol: :http1, reason: reason}
-
-      {:error, %Mint.HTTPError{module: Mint.HTTP2, reason: reason}} ->
-        %Req.HTTPError{protocol: :http2, reason: reason}
-
-      {:error, %Finch.Error{reason: reason}} ->
-        %Req.HTTPError{protocol: :http2, reason: reason}
-
-      {:error, exception} ->
-        exception
-    end
-  end
-
-  defp add_private_options(finch_request, nil), do: finch_request
-
-  defp add_private_options(finch_request, private_options)
-       when is_list(private_options) or is_map(private_options) do
-    Enum.reduce(private_options, finch_request, fn {k, v}, acc_finch_req ->
-      Finch.Request.put_private(acc_finch_req, k, v)
-    end)
-  end
-
-  defp finch_fields_to_map(fields) do
-    Enum.reduce(fields, %{}, fn {name, value}, acc ->
-      Map.update(acc, name, [value], &(&1 ++ [value]))
-    end)
-  end
-
-  defp finch_parse_message(ref, {ref, {:data, data}}) do
-    {:ok, [data: data]}
-  end
-
-  defp finch_parse_message(ref, {ref, :done}) do
-    {:ok, [:done]}
-  end
-
-  defp finch_parse_message(ref, {ref, {:trailers, trailers}}) do
-    {:ok, [trailers: trailers]}
-  end
-
-  defp finch_parse_message(ref, {ref, {:error, reason}}) do
-    {:error, reason}
-  end
-
-  defp finch_cancel(ref) do
-    Finch.cancel_async_request(ref)
-  end
-
-  defp finch_name(request) do
-    if name = request.options[:finch] do
-      if request.options[:connect_options] do
-        raise ArgumentError, "cannot set both :finch and :connect_options"
-      end
-
-      name
-    else
-      connect_options = request.options[:connect_options] || []
-      inet6_options = if request.options[:inet6], do: [inet6: true], else: []
-
-      if connect_options != [] || inet6_options != [] do
-        Req.Request.validate_options(
-          connect_options,
-          MapSet.new([
-            :timeout,
-            :protocols,
-            :transport_opts,
-            :proxy_headers,
-            :proxy,
-            :client_settings,
-            :hostname,
-
-            # TODO: Remove on Req v1.0
-            :protocol
-          ])
-        )
-
-        hostname_opts = Keyword.take(connect_options, [:hostname])
-
-        transport_opts = [
-          transport_opts:
-            Keyword.merge(
-              Keyword.take(connect_options, [:timeout]) ++ inet6_options,
-              Keyword.get(connect_options, :transport_opts, [])
-            )
-        ]
-
-        proxy_headers_opts = Keyword.take(connect_options, [:proxy_headers])
-        proxy_opts = Keyword.take(connect_options, [:proxy])
-        client_settings_opts = Keyword.take(connect_options, [:client_settings])
-
-        protocols =
-          cond do
-            protocols = connect_options[:protocols] ->
-              protocols
-
-            protocol = connect_options[:protocol] ->
-              IO.warn([
-                "setting `connect_options: [protocol: protocol]` is deprecated, ",
-                "use `connect_options: [protocols: protocols]` instead"
-              ])
-
-              [protocol]
-
-            true ->
-              Req.Application.__default_protocols__()
-          end
-
-        pool_opts = [
-          conn_opts:
-            hostname_opts ++
-              transport_opts ++
-              proxy_headers_opts ++
-              proxy_opts ++
-              client_settings_opts,
-          protocols: protocols
-        ]
-
-        name =
-          [connect_options, inet6_options]
-          |> :erlang.term_to_binary()
-          |> :erlang.md5()
-          |> Base.url_encode64(padding: false)
-
-        name = Module.concat(Req.FinchSupervisor, "Pool_#{name}")
-
-        case DynamicSupervisor.start_child(
-               Req.FinchSupervisor,
-               {Finch, name: name, pools: %{default: pool_opts}}
-             ) do
-          {:ok, _} ->
-            name
-
-          {:error, {:already_started, _}} ->
-            name
-        end
-      else
-        Req.Finch
-      end
-    end
+    Req.Finch.run(request)
   end
 
   @doc """


Test patch:
diff --git a/test/req/finch_test.exs b/test/req/finch_test.exs
new file mode 100644
index 0000000..7c50057
--- /dev/null
+++ b/test/req/finch_test.exs
@@ -0,0 +1,54 @@
+defmodule Req.FinchTest do
+  use ExUnit.Case, async: true
+
+  describe "pool_options" do
+    test "defaults" do
+      assert Req.Finch.pool_options([]) ==
+               [
+                 protocols: [:http1]
+               ]
+    end
+
+    test "ipv6" do
+      assert Req.Finch.pool_options(inet6: true) ==
+               [
+                 protocols: [:http1],
+                 conn_opts: [transport_opts: [inet6: true]]
+               ]
+    end
+
+    test "connect_options protocols" do
+      assert Req.Finch.pool_options(connect_options: [protocols: [:http2]]) ==
+               [
+                 protocols: [:http2]
+               ]
+    end
+
+    test "connect_options timeout" do
+      assert Req.Finch.pool_options(connect_options: [timeout: 0]) ==
+               [
+                 protocols: [:http1],
+                 conn_opts: [transport_opts: [timeout: 0]]
+               ]
+    end
+
+    test "connect_options transport_opts" do
+      assert Req.Finch.pool_options(connect_options: [transport_opts: [cacerts: []]]) ==
+               [
+                 protocols: [:http1],
+                 conn_opts: [transport_opts: [cacerts: []]]
+               ]
+    end
+
+    test "connect_options transport_opts + timeout + ipv6" do
+      assert Req.Finch.pool_options(
+               connect_options: [timeout: 0, transport_opts: [cacerts: []]],
+               inet6: true
+             ) ==
+               [
+                 protocols: [:http1],
+                 conn_opts: [transport_opts: [timeout: 0, inet6: true, cacerts: []]]
+               ]
+    end
+  end
+end