Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F112369
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 23, 11:27 AM (21 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38951
Default Alt Text
(17 KB)
Attached To
Mode
R28 tesla
Attached
Detach File
Event Timeline
Log In to Comment