Page MenuHomePhorge

No OneTemporary

Size
17 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/tesla/adapter/gun.ex b/lib/tesla/adapter/gun.ex
new file mode 100644
index 0000000..0f31f19
--- /dev/null
+++ b/lib/tesla/adapter/gun.ex
@@ -0,0 +1,209 @@
+if Code.ensure_loaded?(:gun) do
+ defmodule Tesla.Adapter.Gun do
+ @moduledoc """
+ Adapter for [gun] https://github.com/ninenines/gun
+
+ Remember to add `{:gun, "~> 1.3"}` to dependencies
+ Also, you need to recompile tesla after adding `:gun` dependency:
+ ```
+ mix deps.clean tesla
+ mix deps.compile tesla
+ ```
+ ### Example usage
+ ```
+ # set globally in config/config.exs
+ config :tesla, :adapter, Pleroma.Tesla.Adapter.Gun
+ # set per module
+ defmodule MyClient do
+ use Tesla
+ adapter Pleroma.Tesla.Adapter.Gun
+ end
+ ```
+
+ ### Options:
+
+ * `connect_timeout` - Connection timeout.
+ * `http_opts` - Options specific to the HTTP protocol.
+ * `http2_opts` - Options specific to the HTTP/2 protocol.
+ * `protocols` - Ordered list of preferred protocols. Defaults: [http2, http] - for :tls, [http] - for :tcp.
+ * `retry` - Number of times Gun will try to reconnect on failure before giving up. Default: 5
+ * `retry_timeout` - Time between retries in milliseconds. Default: 5000
+ * `trace` - Whether to enable dbg tracing of the connection process. Should only be used during debugging. Default: false.
+ * `transport` - Whether to use TLS or plain TCP. The default varies depending on the port used. Port 443 defaults to tls.
+ All other ports default to tcp.
+ * `transport_opts` - Transport options. They are TCP options or TLS options depending on the selected transport. Default: [].
+ * `ws_opts` - Options specific to the Websocket protocol. Default: %{}.
+ * `compress` - Whether to enable permessage-deflate compression. This does not guarantee that compression will
+ be used as it is the server that ultimately decides. Defaults to false.
+ * `protocols` - A non-empty list enables Websocket protocol negotiation. The list of protocols will be sent
+ in the sec-websocket-protocol request header.
+ The handler module interface is currently undocumented and must be set to `gun_ws_h`.
+ }
+
+ """
+ @behaviour Tesla.Adapter
+ alias Tesla.Multipart
+
+ @default [
+ connect_timeout: 1000,
+ retry: 3,
+ retry_timeout: 1000,
+ max_body: 2_000_000,
+ timeout: 2_000
+ ]
+
+ @gun_keys [
+ :connect_timeout,
+ :http_opts,
+ :http2_opts,
+ :protocols,
+ :retry,
+ :retry_timeout,
+ :trace,
+ :transport,
+ :transport_opts,
+ :ws_opts
+ ]
+
+ @impl true
+ @doc false
+ def call(env, opts) do
+ with {:ok, status, headers, body} <- request(env, opts) do
+ {:ok, %{env | status: status, headers: format_headers(headers), body: body}}
+ end
+ end
+
+ defp format_headers(headers) do
+ for {key, value} <- headers do
+ {String.downcase(to_string(key)), to_string(value)}
+ end
+ end
+
+ defp format_method(method), do: String.upcase(to_string(method))
+
+ defp format_url(nil, nil), do: ""
+ defp format_url(nil, query), do: "?" <> query
+ defp format_url(path, nil), do: path
+ defp format_url(path, query), do: path <> "?" <> query
+
+ defp request(env, opts) do
+ request(
+ format_method(env.method),
+ Tesla.build_url(env.url, env.query),
+ env.headers,
+ env.body || "",
+ Tesla.Adapter.opts(@default, env, opts) |> Enum.into(%{})
+ )
+ end
+
+ defp request(method, url, headers, %Stream{} = body, opts),
+ do: request_stream(method, url, headers, body, opts)
+
+ defp request(method, url, headers, body, opts) when is_function(body),
+ do: request_stream(method, url, headers, body, opts)
+
+ defp request(method, url, headers, %Multipart{} = mp, opts) do
+ headers = headers ++ Multipart.headers(mp)
+ body = Multipart.body(mp)
+
+ request(method, url, headers, body, opts)
+ end
+
+ defp request(method, url, headers, body, opts) do
+ with {pid, f_url} <- open_conn(url, opts),
+ stream <- open_stream(pid, method, f_url, headers, body, false) do
+ read_response(pid, stream, opts)
+ end
+ end
+
+ defp request_stream(method, url, headers, body, opts) do
+ with {pid, f_url} <- open_conn(url, opts),
+ stream <- open_stream(pid, method, f_url, headers, body, true) do
+ read_response(pid, stream, opts)
+ end
+ end
+
+ defp open_conn(url, opts) do
+ uri = URI.parse(url)
+ opts = if uri.scheme == "https", do: Map.put(opts, :transport, :tls), else: opts
+ {:ok, pid} = :gun.open(to_charlist(uri.host), uri.port, Map.take(opts, @gun_keys))
+ {pid, format_url(uri.path, uri.query)}
+ end
+
+ defp open_stream(pid, method, url, headers, body, true) do
+ stream = :gun.request(pid, method, url, headers, "")
+ for data <- body, do: :ok = :gun.data(pid, stream, :nofin, data)
+ :gun.data(pid, stream, :fin, "")
+ stream
+ end
+
+ defp open_stream(pid, method, url, headers, body, false),
+ do: :gun.request(pid, method, url, headers, body)
+
+ defp read_response(pid, stream, opts) do
+ receive do
+ msg ->
+ case msg do
+ {:gun_response, ^pid, ^stream, :fin, status, headers} ->
+ {:ok, status, headers, ""}
+
+ {:gun_response, ^pid, ^stream, :nofin, status, headers} ->
+ mref = Process.monitor(pid)
+
+ case read_body(pid, stream, mref, opts) do
+ {:error, error} ->
+ {:error, error}
+
+ body ->
+ {:ok, status, headers, body}
+ end
+
+ {:error, error} ->
+ {:error, error}
+
+ {:gun_up, ^pid, :http} ->
+ read_response(pid, stream, opts)
+
+ msg ->
+ IO.inspect(msg)
+ IO.inspect("unsupported message received in read response.")
+ msg
+ end
+ after
+ opts[:timeout] ->
+ {:error, "read response timeout"}
+ end
+ end
+
+ defp read_body(pid, stream, mref, opts, acc \\ "") do
+ limit = opts[:max_body]
+
+ receive do
+ msg ->
+ case msg do
+ {:gun_data, ^pid, ^stream, :fin, body} ->
+ if limit - byte_size(body) >= 0 do
+ acc <> body
+ else
+ {:error, "body too large"}
+ end
+
+ {:gun_data, ^pid, ^stream, :nofin, part} ->
+ if limit - byte_size(part) >= 0 do
+ read_body(pid, stream, mref, opts, acc <> part)
+ else
+ {:error, "body too large"}
+ end
+
+ msg ->
+ IO.inspect(msg)
+ IO.inspect("unsupported message received in read body.")
+ msg
+ end
+ after
+ opts[:timeout] ->
+ {:error, "read body timeout"}
+ end
+ end
+ end
+end
diff --git a/mix.exs b/mix.exs
index b2d5f87..2fab34c 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,128 +1,131 @@
defmodule Tesla.Mixfile do
use Mix.Project
@version "1.2.1"
def project do
[
app: :tesla,
version: @version,
description: description(),
package: package(),
source_ref: "v#{@version}",
source_url: "https://github.com/teamon/tesla",
elixir: "~> 1.4",
elixirc_paths: elixirc_paths(Mix.env()),
deps: deps(),
lockfile: lockfile(System.get_env("LOCKFILE")),
test_coverage: [tool: ExCoveralls],
dialyzer: [
plt_add_apps: [:inets],
plt_add_deps: :project
],
docs: docs()
]
end
# Configuration for the OTP application
#
# Type `mix help compile.app` for more information
def application do
[applications: applications(Mix.env())]
end
- def applications(:test), do: applications(:dev) ++ [:httparrot, :hackney, :ibrowse]
+ def applications(:test), do: applications(:dev) ++ [:httparrot, :hackney, :ibrowse, :gun]
def applications(_), do: [:logger, :ssl, :inets]
defp description do
"HTTP client library, with support for middleware and multiple adapters."
end
defp package do
[
maintainers: ["Tymon Tobolski"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/teamon/tesla"}
]
end
# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp lockfile(nil), do: "mix.lock"
defp lockfile(lockfile), do: "test/lockfiles/#{lockfile}.lock"
defp deps do
[
{:mime, "~> 1.0"},
# http clients
{:ibrowse, "~> 4.4.0", optional: true},
{:hackney, "~> 1.6", optional: true},
+ {:gun, "~> 1.3", optional: true},
# json parsers
{:jason, ">= 1.0.0", optional: true},
{:poison, ">= 1.0.0", optional: true},
{:exjsx, ">= 3.0.0", optional: true},
# other
{:fuse, "~> 2.4", optional: true},
{:telemetry, "~> 0.3", optional: true},
+ {:cowlib, "~> 1.0.2", override: true},
# testing & docs
{:excoveralls, "~> 0.8", only: :test},
{:httparrot, "~> 1.0", only: :test},
{:ex_doc, github: "elixir-lang/ex_doc", only: :dev},
{:mix_test_watch, "~> 0.5", only: :dev},
{:dialyxir, "~> 1.0.0-rc.3", only: [:dev, :test]},
{:inch_ex, "~> 0.5.6", only: :docs}
]
end
defp docs do
[
main: "readme",
extras: ["README.md"],
groups_for_modules: [
Behaviours: [
Tesla.Adapter,
Tesla.Middleware
],
Adapters: [
Tesla.Adapter.Hackney,
Tesla.Adapter.Httpc,
- Tesla.Adapter.Ibrowse
+ Tesla.Adapter.Ibrowse,
+ Tesla.Adapter.Gun
],
Middlewares: [
Tesla.Middleware.BaseUrl,
Tesla.Middleware.BasicAuth,
Tesla.Middleware.CompressRequest,
Tesla.Middleware.Compression,
Tesla.Middleware.DecodeJson,
Tesla.Middleware.DecodeRels,
Tesla.Middleware.DecompressResponse,
Tesla.Middleware.DigestAuth,
Tesla.Middleware.EncodeJson,
Tesla.Middleware.FollowRedirects,
Tesla.Middleware.FormUrlencoded,
Tesla.Middleware.Fuse,
Tesla.Middleware.Headers,
Tesla.Middleware.JSON,
Tesla.Middleware.KeepRequest,
Tesla.Middleware.Logger,
Tesla.Middleware.MethodOverride,
Tesla.Middleware.Opts,
Tesla.Middleware.Query,
Tesla.Middleware.Retry,
Tesla.Middleware.Telemetry,
Tesla.Middleware.Timeout
]
],
nest_modules_by_prefix: [
Tesla.Adapter,
Tesla.Middleware
]
]
end
end
diff --git a/mix.lock b/mix.lock
index ed6bde6..79b83a3 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,35 +1,36 @@
%{
"certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"con_cache": {:hex, :con_cache, "0.12.1", "7553dcd51ee86fd52bd9ea9aa4b33e71bebf0b5fc5ab60e63d2e0bcaa260f937", [:mix], [{:exactor, "~> 2.2.0", [hex: :exactor, repo: "hexpm", optional: false]}], "hexpm"},
"cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
"dialyxir": {:hex, :dialyxir, "1.0.0-rc.3", "774306f84973fc3f1e2e8743eeaa5f5d29b117f3916e5de74c075c02f1b8ef55", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.3.0", "17f0c38eaafb4800f746b457313af4b2442a8c2405b49c645768680f900be603", [:mix], [], "hexpm"},
"ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "6b146b00eee65d989361c1674e531aa46543243c", []},
"exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm"},
"excoveralls": {:hex, :excoveralls, "0.9.1", "14fd20fac51ab98d8e79615814cc9811888d2d7b28e85aa90ff2e30dcf3191d6", [:mix], [{:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
"file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm"},
"fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], [], "hexpm"},
"fuse": {:hex, :fuse, "2.4.2", "9106b08db8793a34cc156177d7e24c41bd638ee1b28463cb76562fde213e8ced", [:rebar3], [], "hexpm"},
+ "gun": {:hex, :gun, "1.3.0", "18e5d269649c987af95aec309f68a27ffc3930531dd227a6eaa0884d6684286e", [:rebar3], [{:cowlib, "~> 2.6.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm"},
"hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httparrot": {:hex, :httparrot, "1.0.2", "679b86df6fb82423a345e91aa6a1f7e12cc6c2480873c842e4acd3caa13bc6ab", [:mix], [{:con_cache, "~> 0.12.0", [hex: :con_cache, repo: "hexpm", optional: false]}, {:cowboy, "~> 1.1.2", [hex: :cowboy, repo: "hexpm", optional: false]}, {:exjsx, "~> 3.0 or ~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}], "hexpm"},
"ibrowse": {:hex, :ibrowse, "4.4.0", "2d923325efe0d2cb09b9c6a047b2835a5eda69d8a47ed6ff8bc03628b764e991", [:rebar3], [], "hexpm"},
"idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"inch_ex": {:hex, :inch_ex, "0.5.6", "418357418a553baa6d04eccd1b44171936817db61f4c0840112b420b8e378e67", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.1", "d3ccb840dfb06f2f90a6d335b536dd074db748b3e7f5b11ab61d239506585eb2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"},
"makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"mix_test_watch": {:hex, :mix_test_watch, "0.7.0", "205f77063ed9b81ca41a2ab78486c653f9bfb1e5a341b5cf8fc2747dae67b0df", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
}
diff --git a/test/tesla/adapter/gun_test.exs b/test/tesla/adapter/gun_test.exs
new file mode 100644
index 0000000..aea7b47
--- /dev/null
+++ b/test/tesla/adapter/gun_test.exs
@@ -0,0 +1,30 @@
+defmodule Tesla.Adapter.GunTest do
+ use ExUnit.Case
+
+ use Tesla.AdapterCase, adapter: Tesla.Adapter.Gun
+ use Tesla.AdapterCase.Basic
+ use Tesla.AdapterCase.Multipart
+ use Tesla.AdapterCase.StreamRequestBody
+ use Tesla.AdapterCase.SSL
+
+ test "timeout option" do
+ request = %Env{
+ method: :get,
+ url: "#{@http}/delay/2"
+ }
+
+ assert {:error, "read response timeout"} = Tesla.Adapter.Gun.call(request, timeout: 1_000)
+ end
+
+ test "max_body option" do
+ request = %Env{
+ method: :get,
+ url: "#{@http}/get",
+ query: [
+ message: "Hello world!"
+ ]
+ }
+
+ assert {:error, "body too large"} = Tesla.Adapter.Gun.call(request, max_body: 5)
+ end
+end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 23, 11:27 AM (1 d, 3 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38951
Default Alt Text
(17 KB)

Event Timeline