Page MenuHomePhorge

No OneTemporary

Size
8 KB
Referenced Files
None
Subscribers
None
diff --git a/config/config.exs b/config/config.exs
index 0548fa0..2c43344 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -1,23 +1,24 @@
use Mix.Config
config :tesla, adapter: Tesla.Adapter.Httpc
if Mix.env() == :test do
config :logger, :console,
level: :debug,
format: "$date $time [$level] $metadata$message\n"
config :httparrot,
http_port: 5080,
https_port: 5443,
ssl: true,
unix_socket: false
config :sasl,
errlog_type: :error,
sasl_error_logger: false
config :tesla, MockClient, adapter: Tesla.Mock
- config :tesla, Mint, cacert: ["./deps/httparrot/priv/ssl/server-ca.crt"]
+ config :tesla, Tesla.Adapter.Mint,
+ cacert: ["./deps/httparrot/priv/ssl/server-ca.crt"]
end
diff --git a/lib/tesla/adapter/mint.ex b/lib/tesla/adapter/mint.ex
index 9219880..da8c4dd 100644
--- a/lib/tesla/adapter/mint.ex
+++ b/lib/tesla/adapter/mint.ex
@@ -1,179 +1,186 @@
defmodule Tesla.Adapter.Mint do
@moduledoc """
Adapter for [mint](https://github.com/ericmj/mint)
Caution: The minimum supported Elixir version for mint is 1.5.0
Remember to add `{:mint, "~> 0.2.0"}` and `{:castore, "~> 0.1.0"}` to dependencies
Also, you need to recompile tesla after adding `:mint` dependency:
```
mix deps.clean tesla
mix deps.compile tesla
```
### Example usage
```
# set globally in config/config.exs
config :tesla, :adapter, Tesla.Adapter.Mint
# set per module
defmodule MyClient do
use Tesla
adapter Tesla.Adapter.Mint
end
# set custom cacert
config :tesla, Mint, cacert: ["path_to_cacert"]
"""
@behaviour Tesla.Adapter
import Tesla.Adapter.Shared, only: [stream_to_fun: 1, next_chunk: 1]
alias Tesla.Multipart
alias Mint.HTTP
+ @default adapter: [timeout: 2_000]
+
@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
+ opts = Tesla.Adapter.opts(@default, env, opts)
- defp format_headers(headers) do
- for {key, value} <- headers do
- {String.downcase(to_string(key)), to_string(value)}
+ with {:ok, status, headers, body} <- request(env, opts) do
+ {:ok, %{env | status: status, headers: headers, body: body}}
end
end
defp request(env, opts) do
# Break the URI
%URI{host: host, scheme: scheme, port: port, path: path, query: query} = URI.parse(env.url)
query = (query || "") |> URI.decode_query() |> Map.to_list()
path = Tesla.build_url(path, env.query ++ query)
method = env.method |> Atom.to_string() |> String.upcase()
opts =
if opts |> get_in([:transport_opts, :cacertfile]) |> is_nil() && scheme == "https" &&
!is_nil(get_default_ca()) do
transport =
opts
|> Access.get(:transport_opts, [])
- |> update_in([:cacertfile], fn _ ->
- get_default_ca()
- end)
+ |> Keyword.put(:cacertfile, get_default_ca())
- update_in(opts, [:transport_opts], fn _ ->
- transport
- end)
+ Keyword.put(opts, :transport_opts, transport)
else
opts
end
request(
method,
scheme,
host,
port,
path,
env.headers,
env.body,
opts
)
end
defp request(method, scheme, host, port, path, headers, %Stream{} = body, opts) do
fun = stream_to_fun(body)
request(method, scheme, host, port, path, headers, fun, opts)
end
defp request(method, scheme, host, port, path, headers, %Multipart{} = body, opts) do
headers = headers ++ Multipart.headers(body)
fun = stream_to_fun(Multipart.body(body))
request(method, scheme, host, port, path, headers, fun, opts)
end
defp request(method, scheme, host, port, path, headers, body, opts) when is_function(body) do
with {:ok, conn} <- HTTP.connect(String.to_atom(scheme), host, port, opts),
# FIXME Stream function in Mint will not append the content length after eof
# This will trigger the failure in unit test
{:ok, body, length} <- stream_request(body),
{:ok, conn, _req_ref} <-
HTTP.request(
conn,
method,
path || "/",
headers ++ [{"content-length", "#{length}"}],
body
),
- {:ok, _conn, res = %{status: status, headers: headers}} <- stream_response(conn) do
+ {:ok, conn, res = %{status: status, headers: headers}} <- stream_response(conn, opts),
+ {:ok, _conn} <- HTTP.close(conn) do
{:ok, status, headers, Map.get(res, :data)}
end
end
defp request(method, scheme, host, port, path, headers, body, opts) do
with {:ok, conn} <- HTTP.connect(String.to_atom(scheme), host, port, opts),
- {:ok, conn, _req_ref} <- HTTP.request(conn, method, path || "/", headers, body || ""),
- {:ok, _conn, res = %{status: status, headers: headers}} <- stream_response(conn) do
+ {:ok, conn, _req_ref} <- HTTP.request(conn, method, path || "/", headers, body),
+ {:ok, conn, res = %{status: status, headers: headers}} <- stream_response(conn, opts),
+ {:ok, _conn} <- HTTP.close(conn) do
{:ok, status, headers, Map.get(res, :data)}
end
end
defp get_default_ca() do
- env = Application.get_env(:tesla, Mint)
+ env = Application.get_env(:tesla, Tesla.Adapter.Mint)
Keyword.get(env, :cacert)
end
defp stream_request(fun, body \\ "") do
case next_chunk(fun) do
{:ok, item, fun} when is_list(item) ->
stream_request(fun, body <> List.to_string(item))
{:ok, item, fun} ->
stream_request(fun, body <> item)
:eof ->
{:ok, body, byte_size(body)}
end
end
- defp stream_response(conn, response \\ %{}) do
+ defp stream_response(conn, opts, response \\ %{}) do
receive do
msg ->
case HTTP.stream(conn, msg) do
{:ok, conn, stream} ->
response =
Enum.reduce(stream, response, fn x, acc ->
case x do
{:status, _req_ref, code} ->
Map.put(acc, :status, code)
{:headers, _req_ref, headers} ->
- Map.put(acc, :headers, headers)
+ Map.put(acc, :headers, Map.get(acc, :headers, []) ++ headers)
{:data, _req_ref, data} ->
Map.put(acc, :data, Map.get(acc, :data, "") <> data)
{:done, _req_ref} ->
Map.put(acc, :done, true)
+ {:error, _req_ref, reason} ->
+ Map.put(acc, :error, reason)
+
_ ->
acc
end
end)
- if Map.get(response, :done) do
- {:ok, conn, Map.drop(response, [:done])}
- else
- stream_response(conn, response)
+ cond do
+ Map.has_key?(response, :error) ->
+ {:error, Map.get(response, :error)}
+
+ Map.has_key?(response, :done) ->
+ {:ok, conn, Map.drop(response, [:done])}
+
+ true ->
+ stream_response(conn, response, opts)
end
{:error, _conn, error, _res} ->
{:error, "Encounter Mint error #{inspect(error)}"}
:unknown ->
{:error, "Encounter unknown error"}
end
+ after
+ opts |> Keyword.get(:adapter) |> Keyword.get(:timeout) ->
+ {:error, "Response timeout"}
end
end
end
diff --git a/test/tesla/adapter/mint_test.exs b/test/tesla/adapter/mint_test.exs
index 5b8f63b..6af16f8 100644
--- a/test/tesla/adapter/mint_test.exs
+++ b/test/tesla/adapter/mint_test.exs
@@ -1,9 +1,18 @@
defmodule Tesla.Adapter.MintTest do
use ExUnit.Case
use Tesla.AdapterCase, adapter: Tesla.Adapter.Mint
use Tesla.AdapterCase.Basic
use Tesla.AdapterCase.Multipart
use Tesla.AdapterCase.StreamRequestBody
use Tesla.AdapterCase.SSL
+
+ test "Delay request" do
+ request = %Env{
+ method: :head,
+ url: "#{@http}/delay/1"
+ }
+
+ assert {:error, "Response timeout"} = call(request, adapter: [timeout: 100])
+ end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 30, 12:43 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41334
Default Alt Text
(8 KB)

Event Timeline