Skip to content
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

Added support for the NO_PROXY env variable #361

Merged
merged 3 commits into from
Nov 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions lib/httpoison/base.ex
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ defmodule HTTPoison.Base do
"https" -> System.get_env("HTTPS_PROXY") || System.get_env("https_proxy")
_ -> nil
end
|> check_no_proxy(request_url)
end

proxy_auth = Keyword.get(options, :proxy_auth)
Expand All @@ -613,6 +614,46 @@ defmodule HTTPoison.Base do
hn_proxy_options
end

defp check_no_proxy(nil, _) do
# Don't bother to check no_proxy if there's no proxy to use anyway.
nil
end

defp check_no_proxy(proxy, request_url) do
request_host = URI.parse(request_url).host

should_bypass_proxy =
get_no_proxy_system_env()
|> String.split(",")
|> Enum.any?(fn domain -> matches_no_proxy_value?(request_host, domain) end)

if should_bypass_proxy do
nil
else
proxy
end
end

defp get_no_proxy_system_env() do
System.get_env("NO_PROXY") || System.get_env("no_PROXY") || System.get_env("no_proxy") || ""
end

defp matches_no_proxy_value?(request_host, no_proxy_value) do
cond do
no_proxy_value == "" -> false
String.starts_with?(no_proxy_value, ".") -> String.ends_with?(request_host, no_proxy_value)
String.contains?(no_proxy_value, "*") -> matches_wildcard?(request_host, no_proxy_value)
true -> request_host == no_proxy_value
end
end

defp matches_wildcard?(request_host, wildcard_domain) do
Regex.escape(wildcard_domain)
|> String.replace("\\*", ".*")
|> Regex.compile!()
|> Regex.match?(request_host)
end

@doc false
@spec request(atom, atom, binary, body, headers, any, fun, fun, fun) ::
{:ok, Response.t() | AsyncResponse.t()} | {:error, Error.t()}
Expand Down
140 changes: 115 additions & 25 deletions test/httpoison_base_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ defmodule HTTPoisonBaseTest do
System.delete_env("http_proxy")
System.delete_env("HTTPS_PROXY")
System.delete_env("https_proxy")
System.delete_env("NO_PROXY")
System.delete_env("no_PROXY")
System.delete_env("no_proxy")
end)

stub(:hackney)
Expand Down Expand Up @@ -116,11 +119,7 @@ defmodule HTTPoisonBaseTest do
end

test "passing proxy option" do
expect(:hackney, :request, fn
:post, "http://localhost", [], "body", [proxy: "proxy"] -> {:ok, 200, "headers", :client}
end)

expect(:hackney, :body, fn _, _ -> {:ok, "response"} end)
expect_hackney_post_with_proxy("http://localhost", "proxy")

assert HTTPoison.post!("localhost", "body", [], proxy: "proxy") ==
%HTTPoison.Response{
Expand Down Expand Up @@ -193,11 +192,7 @@ defmodule HTTPoisonBaseTest do
test "having http_proxy env variable set on http requests" do
System.put_env("HTTP_PROXY", "proxy")

expect(:hackney, :request, fn
:post, "http://localhost", [], "body", [proxy: "proxy"] -> {:ok, 200, "headers", :client}
end)

expect(:hackney, :body, fn _, _ -> {:ok, "response"} end)
expect_hackney_post_with_proxy("http://localhost", "proxy")

assert HTTPoison.post!("localhost", "body") ==
%HTTPoison.Response{
Expand All @@ -211,11 +206,7 @@ defmodule HTTPoisonBaseTest do
test "having http_proxy env variable set on http requests as empty string" do
System.put_env("HTTP_PROXY", "")

expect(:hackney, :request, fn :post, "http://localhost", [], "body", [] ->
{:ok, 200, "headers", :client}
end)

expect(:hackney, :body, fn _, _ -> {:ok, "response"} end)
expect_hackney_post_with_no_proxy("http://localhost")

assert HTTPoison.post!("localhost", "body") ==
%HTTPoison.Response{
Expand All @@ -229,11 +220,7 @@ defmodule HTTPoisonBaseTest do
test "having https_proxy env variable set on https requests" do
System.put_env("HTTPS_PROXY", "proxy")

expect(:hackney, :request, fn :post, "https://localhost", [], "body", [proxy: "proxy"] ->
{:ok, 200, "headers", :client}
end)

expect(:hackney, :body, fn _, _ -> {:ok, "response"} end)
expect_hackney_post_with_proxy("https://localhost", "proxy")

assert HTTPoison.post!("https://localhost", "body") ==
%HTTPoison.Response{
Expand All @@ -247,11 +234,7 @@ defmodule HTTPoisonBaseTest do
test "having https_proxy env variable set on http requests" do
System.put_env("HTTPS_PROXY", "proxy")

expect(:hackney, :request, fn
:post, "http://localhost", [], "body", [] -> {:ok, 200, "headers", :client}
end)

expect(:hackney, :body, fn _, _ -> {:ok, "response"} end)
expect_hackney_post_with_no_proxy("http://localhost")

assert HTTPoison.post!("localhost", "body") ==
%HTTPoison.Response{
Expand All @@ -262,6 +245,113 @@ defmodule HTTPoisonBaseTest do
}
end

test "having matching no_proxy env variable set with proxy env variable" do
# If the variable is specified directly, no_proxy should be ignored.
System.put_env("NO_PROXY", ".somedomain.com")

expect_hackney_post_with_proxy("http://www.somedomain.com", "proxy")

assert HTTPoison.post!("www.somedomain.com", "body", [], proxy: "proxy") ==
%HTTPoison.Response{
status_code: 200,
headers: "headers",
body: "response",
request_url: "http://www.somedomain.com"
}
end

test "having matching no_proxy env variable set with http_proxy env" do
# If the variable is specified indirectly, no_proxy should be used.
System.put_env("HTTP_PROXY", "proxy")
System.put_env("NO_PROXY", ".somedomain.com")

expect_hackney_post_with_no_proxy("http://www.somedomain.com")

assert HTTPoison.post!("www.somedomain.com", "body") ==
%HTTPoison.Response{
status_code: 200,
headers: "headers",
body: "response",
request_url: "http://www.somedomain.com"
}
end

test "having no_proxy env variable set that does not match site" do
System.put_env("HTTP_PROXY", "proxy")
System.put_env("NO_PROXY", ".nonmatching.com")

expect_hackney_post_with_proxy("http://www.somedomain.com", "proxy")

assert HTTPoison.post!("http://www.somedomain.com", "body") ==
%HTTPoison.Response{
status_code: 200,
headers: "headers",
body: "response",
request_url: "http://www.somedomain.com"
}
end

test "having no_proxy env variable with multiple domains" do
System.put_env("HTTP_PROXY", "proxy")
System.put_env("NO_PROXY", ".nonmatching.com,.matching.com")

expect_hackney_post_with_no_proxy("http://www.matching.com")

assert HTTPoison.post!("http://www.matching.com", "body") ==
%HTTPoison.Response{
status_code: 200,
headers: "headers",
body: "response",
request_url: "http://www.matching.com"
}
end

test "having no_proxy env variable with wildcard domains" do
System.put_env("HTTP_PROXY", "proxy")
System.put_env("NO_PROXY", ".nonmatching.com,*.matching.com")

expect_hackney_post_with_no_proxy("http://www.matching.com")

assert HTTPoison.post!("http://www.matching.com", "body") ==
%HTTPoison.Response{
status_code: 200,
headers: "headers",
body: "response",
request_url: "http://www.matching.com"
}
end

test "having no_proxy env variable with non-matching wildcard domains" do
System.put_env("HTTP_PROXY", "proxy")
System.put_env("NO_PROXY", "*.nonmatching.com")

expect_hackney_post_with_proxy("http://www.matching.com", "proxy")

assert HTTPoison.post!("http://www.matching.com", "body") ==
%HTTPoison.Response{
status_code: 200,
headers: "headers",
body: "response",
request_url: "http://www.matching.com"
}
end

defp expect_hackney_post_with_proxy(url, proxy) do
expect_hackney_post(url, proxy: proxy)
end

defp expect_hackney_post_with_no_proxy(url) do
expect_hackney_post(url, [])
end

def expect_hackney_post(url, expected_options) do
expect(:hackney, :request, fn
:post, ^url, [], "body", ^expected_options -> {:ok, 200, "headers", :client}
end)

expect(:hackney, :body, fn _, _ -> {:ok, "response"} end)
end

test "passing ssl option" do
expect(:hackney, :request, fn :post,
"http://localhost",
Expand Down