-
Notifications
You must be signed in to change notification settings - Fork 346
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mint adapter #297
Merged
Merged
Mint adapter #297
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
2acabfa
Setup happy path logic for adapter mint
RyanSiu1995 22e5b44
Handle stream request body for Adapter Mint
RyanSiu1995 eca9cf3
Fix the URI parsing before request in Adapter Mint
RyanSiu1995 ee6a72d
Fix streaming request body problem and add multipart support for adap…
RyanSiu1995 b2117e9
Update the mint adapter docs
RyanSiu1995 e57d697
Throwing error tuple when encountering mint error
RyanSiu1995 0dad600
Fix the unit tests for mint
RyanSiu1995 c28cccc
Update README about the cacert and minimum elixir version for mint
RyanSiu1995 ae07e69
Bump the minimum elixir version as 1.5.0
RyanSiu1995 b487fde
Make the cacert as the property for Mint
RyanSiu1995 2d57f4e
Remove unnecessary check for http method in mint adapter
RyanSiu1995 046822b
Improve the usage in mint library
RyanSiu1995 9e7b7d2
Refactor the default cacert as global cacert
RyanSiu1995 eef0adc
Fix the incorrect order of stream_response
RyanSiu1995 8abab5d
Refactor the codes for mint adapter
RyanSiu1995 b1e5c11
Remove OTP 18.3 due to the unsupport from travis
RyanSiu1995 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
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 global custom cacert | ||
config :tesla, Tesla.Adapter.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 | ||
opts = Tesla.Adapter.opts(@default, env, opts) | ||
|
||
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() | ||
|
||
# Set the global cacert file | ||
opts = | ||
if scheme == "https" && !is_nil(get_global_default_ca()) do | ||
transport_opts = Access.get(opts, :transport_opts, []) | ||
|
||
transport_opts = | ||
Keyword.put( | ||
transport_opts, | ||
:cacertfile, | ||
Keyword.get(transport_opts, :cacertfile, []) ++ get_global_default_ca() | ||
) | ||
|
||
Keyword.put(opts, :transport_opts, transport_opts) | ||
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, 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, opts), | ||
{:ok, _conn} <- HTTP.close(conn) do | ||
{:ok, status, headers, Map.get(res, :data)} | ||
end | ||
end | ||
|
||
defp get_global_default_ca() do | ||
case Application.get_env(:tesla, Tesla.Adapter.Mint) do | ||
nil -> nil | ||
env -> Keyword.get(env, :cacert) | ||
end | ||
RyanSiu1995 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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, opts, response \\ %{}) do | ||
receive do | ||
RyanSiu1995 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
msg -> | ||
case HTTP.stream(conn, msg) do | ||
{:ok, conn, stream} -> | ||
response = | ||
Enum.reduce(stream, response, fn | ||
{:status, _req_ref, code}, acc -> | ||
Map.put(acc, :status, code) | ||
|
||
{:headers, _req_ref, headers}, acc -> | ||
Map.put(acc, :headers, Map.get(acc, :headers, []) ++ headers) | ||
|
||
{:data, _req_ref, data}, acc -> | ||
Map.put(acc, :data, Map.get(acc, :data, "") <> data) | ||
|
||
{:done, _req_ref}, acc -> | ||
Map.put(acc, :done, true) | ||
|
||
{:error, _req_ref, reason}, acc -> | ||
Map.put(acc, :error, reason) | ||
|
||
_, acc -> | ||
acc | ||
end) | ||
|
||
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, opts, response) | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +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 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ericmj This config won't end up in production builds, right? I mean, it will be
nil
by default so it should be safe to reference httparrot here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it will be
nil
by default.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct,
config.exs
is not used from dependencies. So theTesla.Adapter.Mint
key will not be set.