Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F113346
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
20 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/tesla/adapter/hackney.ex b/lib/tesla/adapter/hackney.ex
index f3be928..9b9cb4a 100644
--- a/lib/tesla/adapter/hackney.ex
+++ b/lib/tesla/adapter/hackney.ex
@@ -1,92 +1,92 @@
if Code.ensure_loaded?(:hackney) do
defmodule Tesla.Adapter.Hackney do
@moduledoc """
Adapter for [hackney](https://github.com/benoitc/hackney)
Remember to add `{:hackney, "~> 1.6"}` to dependencies (and `:hackney` to applications in `mix.exs`)
Also, you need to recompile tesla after adding `:hackney` dependency:
```
mix deps.clean tesla
mix deps.compile tesla
```
### Example usage
```
# set globally in config/config.exs
config :tesla, :adapter, :hackney
# set per module
defmodule MyClient do
use Tesla
adapter :hackney
end
```
"""
@behaviour Tesla.Adapter
alias Tesla.Multipart
def call(env, opts) do
with {:ok, status, headers, body} <- request(env, opts || []) do
%{env | status: status, headers: format_headers(headers), body: format_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_body(data) when is_list(data), do: IO.iodata_to_binary(data)
defp format_body(data) when is_binary(data), do: data
defp request(env, opts) do
request(
env.method,
Tesla.build_url(env.url, env.query),
- Enum.into(env.headers, []),
+ env.headers,
env.body,
opts ++ env.opts
)
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
handle(:hackney.request(method, url, headers, body || '', opts))
end
defp request_stream(method, url, headers, body, opts) do
with {:ok, ref} <- :hackney.request(method, url, headers, :stream, opts) do
for data <- body, do: :ok = :hackney.send_body(ref, data)
handle(:hackney.start_response(ref))
else
e -> handle(e)
end
end
defp handle({:error, _} = error), do: error
defp handle({:ok, status, headers}), do: {:ok, status, headers, []}
defp handle({:ok, status, headers, ref}) when is_reference(ref) do
with {:ok, body} <- :hackney.body(ref) do
{:ok, status, headers, body}
end
end
defp handle({:ok, status, headers, body}), do: {:ok, status, headers, body}
end
end
diff --git a/lib/tesla/adapter/httpc.ex b/lib/tesla/adapter/httpc.ex
index eb11908..49a30c0 100644
--- a/lib/tesla/adapter/httpc.ex
+++ b/lib/tesla/adapter/httpc.ex
@@ -1,91 +1,91 @@
defmodule Tesla.Adapter.Httpc do
@moduledoc """
Adapter for [httpc](http://erlang.org/doc/man/httpc.html)
This is the default adapter.
**NOTE** Tesla overrides default autoredirect value with false to ensure
consistency between adapters
"""
@behaviour Tesla.Adapter
import Tesla.Adapter.Shared, only: [stream_to_fun: 1, next_chunk: 1]
alias Tesla.Multipart
@override_defaults autoredirect: false
@http_opts ~w(timeout connect_timeout ssl essl autoredirect proxy_auth version relaxed url_encode)a
def call(env, opts) do
opts = Keyword.merge(@override_defaults, opts || [])
with {:ok, {status, headers, body}} <- request(env, opts) do
format_response(env, status, headers, body)
end
end
defp format_response(env, {_, status, _}, headers, body) do
%{env | status: status, headers: format_headers(headers), body: format_body(body)}
end
# from http://erlang.org/doc/man/httpc.html
# headers() = [header()]
# header() = {field(), value()}
# field() = string()
# value() = string()
defp format_headers(headers) do
for {key, value} <- headers do
{String.downcase(to_string(key)), to_string(value)}
end
end
# from http://erlang.org/doc/man/httpc.html
# string() = list of ASCII characters
# Body = string() | binary()
defp format_body(data) when is_list(data), do: IO.iodata_to_binary(data)
defp format_body(data) when is_binary(data), do: data
defp request(env, opts) do
content_type = to_charlist(Tesla.get_header(env, "content-type") || "")
handle(
request(
env.method || :get,
Tesla.build_url(env.url, env.query) |> to_charlist,
- Enum.into(env.headers, [], fn {k, v} -> {to_charlist(k), to_charlist(v)} end),
+ Enum.map(env.headers, fn {k, v} -> {to_charlist(k), to_charlist(v)} end),
content_type,
env.body,
Keyword.split(opts ++ env.opts, @http_opts)
)
)
end
defp request(method, url, headers, _content_type, nil, {http_opts, opts}) do
:httpc.request(method, {url, headers}, http_opts, opts)
end
defp request(method, url, headers, _content_type, %Multipart{} = mp, opts) do
headers = headers ++ Multipart.headers(mp)
headers = for {key, value} <- headers, do: {to_charlist(key), to_charlist(value)}
{content_type, headers} = Keyword.pop_first(headers, 'Content-Type', 'text/plain')
body = stream_to_fun(Multipart.body(mp))
request(method, url, headers, to_charlist(content_type), body, opts)
end
defp request(method, url, headers, content_type, %Stream{} = body, opts) do
fun = stream_to_fun(body)
request(method, url, headers, content_type, fun, opts)
end
defp request(method, url, headers, content_type, body, opts) when is_function(body) do
body = {:chunkify, &next_chunk/1, body}
request(method, url, headers, content_type, body, opts)
end
defp request(method, url, headers, content_type, body, {http_opts, opts}) do
:httpc.request(method, {url, headers, content_type, body}, http_opts, opts)
end
defp handle({:error, {:failed_connect, _}}), do: {:error, :econnrefused}
defp handle(response), do: response
end
diff --git a/lib/tesla/adapter/ibrowse.ex b/lib/tesla/adapter/ibrowse.ex
index 99fe489..5736369 100644
--- a/lib/tesla/adapter/ibrowse.ex
+++ b/lib/tesla/adapter/ibrowse.ex
@@ -1,90 +1,90 @@
if Code.ensure_loaded?(:ibrowse) do
defmodule Tesla.Adapter.Ibrowse do
@moduledoc """
Adapter for [ibrowse](https://github.com/cmullaparthi/ibrowse)
Remember to add `{:ibrowse, "~> 4.2"}` to dependencies (and `:ibrowse` to applications in `mix.exs`)
Also, you need to recompile tesla after adding `:ibrowse` dependency:
```
mix deps.clean tesla
mix deps.compile tesla
```
### Example usage
```
# set globally in config/config.exs
config :tesla, :adapter, :ibrowse
# set per module
defmodule MyClient do
use Tesla
adapter :ibrowse
end
```
"""
@behaviour Tesla.Adapter
import Tesla.Adapter.Shared, only: [stream_to_fun: 1, next_chunk: 1]
alias Tesla.Multipart
def call(env, opts) do
with {:ok, status, headers, body} <- request(env, opts || []) do
%{env | status: format_status(status), headers: format_headers(headers), body: format_body(body)}
end
end
defp format_status(status) when is_list(status) do
status |> to_string() |> String.to_integer()
end
defp format_headers(headers) do
for {key, value} <- headers do
{String.downcase(to_string(key)), to_string(value)}
end
end
defp format_body(data) when is_list(data), do: IO.iodata_to_binary(data)
defp format_body(data) when is_binary(data), do: data
defp request(env, opts) do
body = env.body || []
handle(
request(
Tesla.build_url(env.url, env.query) |> to_charlist,
- Enum.into(env.headers, []),
+ env.headers,
env.method,
body,
opts ++ env.opts
)
)
end
defp request(url, headers, method, %Multipart{} = mp, opts) do
headers = headers ++ Multipart.headers(mp)
body = stream_to_fun(Multipart.body(mp))
request(url, headers, method, body, opts)
end
defp request(url, headers, method, %Stream{} = body, opts) do
fun = stream_to_fun(body)
request(url, headers, method, fun, opts)
end
defp request(url, headers, method, body, opts) when is_function(body) do
body = {&next_chunk/1, body}
opts = Keyword.put(opts, :transfer_encoding, :chunked)
request(url, headers, method, body, opts)
end
defp request(url, headers, method, body, opts) do
:ibrowse.send_req(url, headers, method, body, opts)
end
defp handle({:error, {:conn_failed, error}}), do: error
defp handle(response), do: response
end
end
diff --git a/lib/tesla/middleware/core.ex b/lib/tesla/middleware/core.ex
index d7ed605..99fdd88 100644
--- a/lib/tesla/middleware/core.ex
+++ b/lib/tesla/middleware/core.ex
@@ -1,153 +1,152 @@
defmodule Tesla.Middleware.Normalize do
@moduledoc false
def call(env, next, _opts) do
env
- |> normalize
|> Tesla.run(next)
|> normalize
end
def normalize({:error, reason}) do
raise %Tesla.Error{message: "adapter error: #{inspect(reason)}", reason: reason}
end
def normalize(env) do
env
|> Map.update!(:headers, &normalize_headers/1)
end
def normalize_headers(headers) when is_map(headers) or is_list(headers) do
Enum.into(headers, %{}, fn {k, v} ->
{k |> to_string |> String.downcase(), v |> to_string}
end)
end
end
defmodule Tesla.Middleware.BaseUrl do
@behaviour Tesla.Middleware
@moduledoc """
Set base URL for all requests.
The base URL will be prepended to request path/url only
if it does not include http(s).
### Example usage
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.BaseUrl, "https://api.github.com"
end
MyClient.get("/path") # equals to GET https://api.github.com/path
MyClient.get("http://example.com/path") # equals to GET http://example.com/path
```
"""
def call(env, next, base) do
env
|> apply_base(base)
|> Tesla.run(next)
end
defp apply_base(env, base) do
if Regex.match?(~r/^https?:\/\//, env.url) do
# skip if url is already with scheme
env
else
%{env | url: join(base, env.url)}
end
end
defp join(base, url) do
case {String.last(to_string(base)), url} do
{nil, url} -> url
{"/", "/" <> rest} -> base <> rest
{"/", rest} -> base <> rest
{_, "/" <> rest} -> base <> "/" <> rest
{_, rest} -> base <> "/" <> rest
end
end
end
defmodule Tesla.Middleware.Headers do
@behaviour Tesla.Middleware
@moduledoc """
Set default headers for all requests
### Example usage
```
defmodule Myclient do
use Tesla
plug Tesla.Middleware.Headers, %{"User-Agent" => "Tesla"}
end
```
"""
def call(env, next, headers) do
env
|> Tesla.put_headers(headers)
|> Tesla.run(next)
end
end
defmodule Tesla.Middleware.Query do
@behaviour Tesla.Middleware
@moduledoc """
Set default query params for all requests
### Example usage
```
defmodule Myclient do
use Tesla
plug Tesla.Middleware.Query, [token: "some-token"]
end
```
"""
def call(env, next, query) do
env
|> merge(query)
|> Tesla.run(next)
end
defp merge(env, nil), do: env
defp merge(env, query) do
Map.update!(env, :query, &(&1 ++ query))
end
end
defmodule Tesla.Middleware.Opts do
@behaviour Tesla.Middleware
@moduledoc """
Set default opts for all requests
### Example usage
```
defmodule Myclient do
use Tesla
plug Tesla.Middleware.Opts, [some: "option"]
end
```
"""
def call(env, next, opts) do
Tesla.run(%{env | opts: env.opts ++ opts}, next)
end
end
defmodule Tesla.Middleware.BaseUrlFromConfig do
def call(env, next, opts) do
base = config(opts)[:base_url]
Tesla.Middleware.BaseUrl.call(env, next, base)
end
defp config(opts) do
Application.get_env(Keyword.fetch!(opts, :otp_app), Keyword.fetch!(opts, :module))
end
end
diff --git a/test/support/adapter_case/basic.ex b/test/support/adapter_case/basic.ex
index 3cc1567..3931585 100644
--- a/test/support/adapter_case/basic.ex
+++ b/test/support/adapter_case/basic.ex
@@ -1,104 +1,104 @@
defmodule Tesla.AdapterCase.Basic do
defmacro __using__(_) do
quote do
alias Tesla.Env
describe "Basic" do
test "HEAD request" do
request = %Env{
method: :head,
url: "#{@url}/ip"
}
assert %Env{} = response = call(request)
assert response.status == 200
end
test "GET request" do
request = %Env{
method: :get,
url: "#{@url}/ip"
}
assert %Env{} = response = call(request)
assert response.status == 200
end
test "POST request" do
request = %Env{
method: :post,
url: "#{@url}/post",
body: "some-post-data",
- headers: %{"Content-Type" => "text/plain"}
+ headers: [{"content-type", "text/plain"}]
}
assert %Env{} = response = call(request)
assert response.status == 200
assert Tesla.get_header(response, "content-type") == "application/json"
assert Regex.match?(~r/some-post-data/, response.body)
end
test "unicode" do
request = %Env{
method: :post,
url: "#{@url}/post",
body: "1 ø 2 đ 1 \u00F8 2 \u0111",
- headers: %{"Content-Type" => "text/plain"}
+ headers: [{"content-type", "text/plain"}]
}
assert %Env{} = response = call(request)
assert response.status == 200
assert Tesla.get_header(response, "content-type") == "application/json"
assert Regex.match?(~r/1 ø 2 đ 1 ø 2 đ/, response.body)
end
test "passing query params" do
request = %Env{
method: :get,
url: "#{@url}/get",
query: [
page: 1,
sort: "desc",
status: ["a", "b", "c"],
user: [name: "Jon", age: 20]
]
}
assert %Env{} = response = call(request)
assert response.status == 200
response = Tesla.Middleware.JSON.decode(response, [])
args = response.body["args"]
assert args["page"] == "1"
assert args["sort"] == "desc"
assert args["status[]"] == ["a", "b", "c"]
assert args["user[name]"] == "Jon"
assert args["user[age]"] == "20"
end
test "autoredirects disabled by default" do
request = %Env{
method: :get,
url: "#{@url}/redirect-to?url=#{@url}/status/200"
}
assert %Env{} = response = call(request)
assert response.status == 301
end
test "error: connection refused" do
request = %Env{
method: :get,
url: "http://localhost:1234"
}
assert_raise Tesla.Error, fn ->
call(request)
end
end
end
end
end
end
diff --git a/test/support/adapter_case/stream_request_body.ex b/test/support/adapter_case/stream_request_body.ex
index 29e98aa..03fbc39 100644
--- a/test/support/adapter_case/stream_request_body.ex
+++ b/test/support/adapter_case/stream_request_body.ex
@@ -1,42 +1,42 @@
defmodule Tesla.AdapterCase.StreamRequestBody do
defmacro __using__(_) do
quote do
alias Tesla.Env
describe "Stream" do
test "stream request body: Stream.map" do
request = %Env{
method: :post,
url: "#{@url}/post",
- headers: %{"Content-Type" => "text/plain"},
+ headers: [{"content-type", "text/plain"}],
body: Stream.map(1..5, &to_string/1)
}
assert %Env{} = response = call(request)
assert response.status == 200
assert Regex.match?(~r/12345/, to_string(response.body))
end
test "stream request body: Stream.unfold" do
body =
Stream.unfold(5, fn
0 -> nil
n -> {n, n - 1}
end)
|> Stream.map(&to_string/1)
request = %Env{
method: :post,
url: "#{@url}/post",
- headers: %{"Content-Type" => "text/plain"},
+ headers: [{"content-type", "text/plain"}],
body: body
}
assert %Env{} = response = call(request)
assert response.status == 200
assert Regex.match?(~r/54321/, to_string(response.body))
end
end
end
end
end
diff --git a/test/tesla/middleware/digest_auth_test.exs b/test/tesla/middleware/digest_auth_test.exs
index 22302ca..3de7cbc 100644
--- a/test/tesla/middleware/digest_auth_test.exs
+++ b/test/tesla/middleware/digest_auth_test.exs
@@ -1,101 +1,96 @@
defmodule Tesla.Middleware.DigestAuthTest do
use ExUnit.Case, async: false
defmodule DigestClient do
use Tesla
adapter fn env ->
- case env do
- %{url: "/no-digest-auth"} ->
+ cond do
+ env.url == "/no-digest-auth" ->
env
- %{headers: %{"authorization" => _}} ->
+ Tesla.get_header(env, "authorization") ->
env
- _ ->
- %{
- env
- | headers: %{
- "WWW-Authenticate" => """
- Digest realm="testrealm@host.com",
- qop="auth,auth-int",
- nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
- opaque="5ccc069c403ebaf9f0171e9517f40e41"
- """
- }
- }
+ true ->
+ Tesla.put_headers(env, [{"WWW-Authenticate", """
+ Digest realm="testrealm@host.com",
+ qop="auth,auth-int",
+ nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
+ opaque="5ccc069c403ebaf9f0171e9517f40e41"
+ """}])
end
end
def client(username, password, opts \\ %{}) do
Tesla.build_client([
{
Tesla.Middleware.DigestAuth,
Map.merge(
%{
username: username,
password: password,
cnonce_fn: fn -> "0a4f113b" end,
nc: "00000001"
},
opts
)
}
])
end
end
defmodule DigestClientWithDefaults do
use Tesla
def client do
Tesla.build_client([
{Tesla.Middleware.DigestAuth, nil}
])
end
end
test "sends request with proper authorization header" do
request =
DigestClient.client("Mufasa", "Circle Of Life") |> DigestClient.get("/dir/index.html")
auth_header = Tesla.get_header(request, "authorization")
assert auth_header =~ ~r/^Digest /
assert auth_header =~ "username=\"Mufasa\""
assert auth_header =~ "realm=\"testrealm@host.com\""
assert auth_header =~ "algorithm=MD5"
assert auth_header =~ "qop=auth"
assert auth_header =~ "uri=\"/dir/index.html\""
assert auth_header =~ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\""
assert auth_header =~ "nc=00000001"
assert auth_header =~ "cnonce=\"0a4f113b\""
assert auth_header =~ "response=\"6629fae49393a05397450978507c4ef1\""
end
test "has default values for username and cn" do
response = DigestClientWithDefaults.client() |> DigestClient.get("/")
auth_header = Tesla.get_header(response, "authorization")
assert auth_header =~ "username=\"\""
assert auth_header =~ "nc=00000000"
end
test "generates different cnonce with each request by default" do
request = fn -> DigestClientWithDefaults.client() |> DigestClient.get("/") end
cnonce_1 = Regex.run(~r/cnonce="(.*?)"/, Tesla.get_header(request.(), "authorization")) |> Enum.at(1)
cnonce_2 = Regex.run(~r/cnonce="(.*?)"/, Tesla.get_header(request.(), "authorization")) |> Enum.at(1)
assert cnonce_1 != cnonce_2
end
test "works when passing custom opts" do
request = DigestClientWithDefaults.client() |> DigestClient.get("/", opts: [hodor: "hodor"])
assert request.opts == [hodor: "hodor"]
end
test "ignores digest auth when server doesn't respond with www-authenticate header" do
response = DigestClientWithDefaults.client() |> DigestClient.get("/no-digest-auth")
refute Tesla.get_header(response, "authorization")
end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Nov 25, 3:52 AM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39571
Default Alt Text
(20 KB)
Attached To
Mode
R28 tesla
Attached
Detach File
Event Timeline
Log In to Comment