aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkballou <kballou@devnulllabs.io>2016-07-28 16:44:32 -0600
committerkballou <kballou@devnulllabs.io>2016-07-29 11:36:04 -0600
commit1248eeb88d90f0ece972299d40b7406ef0a17cf5 (patch)
tree813c9c8c899a5752409641e47c4480f5e0308721
parent2175a2f4d9a416f4a443166a2487c8c12a3df9e2 (diff)
downloadexping-1248eeb88d90f0ece972299d40b7406ef0a17cf5.tar.gz
exping-1248eeb88d90f0ece972299d40b7406ef0a17cf5.tar.xz
Add HTTP "Ping"
-rw-r--r--config/test.exs7
-rw-r--r--lib/exping.ex20
-rw-r--r--lib/exping/http.ex45
-rw-r--r--lib/exping/http/task.ex45
-rw-r--r--lib/exping/supervisor.ex1
-rw-r--r--test/http_test.exs36
-rw-r--r--test/support/test_http_client.ex48
7 files changed, 202 insertions, 0 deletions
diff --git a/config/test.exs b/config/test.exs
index d2d855e..2f71b61 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -1 +1,8 @@
use Mix.Config
+
+config :exping, http_client: ExPing.Test.HTTPClient
+
+config :exping, :http,
+ timeout: 10
+
+config :logger, level: :warn
diff --git a/lib/exping.ex b/lib/exping.ex
index 056bfda..c63c37a 100644
--- a/lib/exping.ex
+++ b/lib/exping.ex
@@ -1,5 +1,25 @@
defmodule ExPing do
+ require Logger
@moduledoc """
Public API for ExPing
"""
+
+ @pings [&ExPing.HTTP.get/1, &ExPing.HTTP.head/1]
+
+ @spec ping(URI.t) :: boolean
+ def ping(address) do
+ :ok = Logger.info("Attempting to ping #{inspect(address)}")
+ @pings
+ |> Enum.map(&spawn_ping(&1, address))
+ |> Enum.all?
+ end
+
+ @spec spawn_ping(fun, URI.t) :: boolean
+ defp spawn_ping(ping, address) do
+ case ping.(address) do
+ {:ok, _} -> true
+ {:error, _} -> false
+ end
+ end
+
end
diff --git a/lib/exping/http.ex b/lib/exping/http.ex
new file mode 100644
index 0000000..576562f
--- /dev/null
+++ b/lib/exping/http.ex
@@ -0,0 +1,45 @@
+defmodule ExPing.HTTP do
+ require Logger
+ @moduledoc """
+ Provides basic HTTP client for pinging endpoints
+ """
+
+ @timeout Application.get_env(:exping, :http)[:timeout] || 5000
+
+ @spec head(URI.t) :: {:ok, {integer, binary}} | {:error, term}
+ def head(endpoint) do
+ ref = make_ref()
+ :ok = Logger.info("Sending HEAD request to #{inspect(endpoint)}")
+ {:ok, _} = spawn_http_task(:head, [endpoint, ref, self()])
+
+ receive do
+ {:http_task_resp, ^ref, {:ok, {_, _}} = resp} -> resp
+ {:http_task_resp, ^ref, {:error, _} = error} -> error
+ after @timeout ->
+ {:error, :timeout}
+ end
+ end
+
+ @spec get(URI.t) :: {:ok, {integer, binary}} | {:error, term}
+ def get(endpoint) do
+ ref = make_ref()
+ :ok = Logger.info("Sending GET request to #{inspect(endpoint)}")
+ {:ok, _} = spawn_http_task(:get, [endpoint, ref, self()])
+
+ receive do
+ {:http_task_resp, ^ref, {:ok, _} = resp} -> resp
+ {:http_task_resp, ^ref, {:error, _} = error} -> error
+ after @timeout ->
+ {:error, :timeout}
+ end
+ end
+
+ defp spawn_http_task(method, args) do
+ Task.Supervisor.start_child(
+ ExPing.Supervisor.Task,
+ ExPing.HTTP.Task,
+ method,
+ args)
+ end
+
+end
diff --git a/lib/exping/http/task.ex b/lib/exping/http/task.ex
new file mode 100644
index 0000000..f9d39f5
--- /dev/null
+++ b/lib/exping/http/task.ex
@@ -0,0 +1,45 @@
+defmodule ExPing.HTTP.Task do
+ require Logger
+ @moduledoc """
+ Task module for performing HTTP requests
+ """
+
+ @user_agent {'user-agent', 'exping'}
+
+ @http Application.get_env(:exping, :http_client) || :httpc
+
+ @type response :: {integer, binary}
+
+ @spec head(URI.t, reference, pid) :: {:ok, response} | {:error, term}
+ def head(%URI{} = uri, ref, owner) do
+ :head
+ |> @http.request({endpoint(uri), headers()}, [], [])
+ |> process_response(ref, owner)
+ end
+
+ @spec get(URI.t, reference, pid) :: {:ok, response} | {:error, term}
+ def get(%URI{} = uri, ref, owner) do
+ :get
+ |> @http.request({endpoint(uri), headers()}, [], [])
+ |> process_response(ref, owner)
+ end
+
+ defp process_response({:ok, {{_, code, _}, _, body}}, ref, owner) do
+ :ok = Logger.info("HTTP request returned: #{code}")
+ send(owner, {:http_task_resp, ref, {:ok, {code, body}}})
+ end
+
+ defp process_response({:error, _} = error, ref, owner) do
+ :ok = Logger.warn("HTTP request returned error: #{inspect(error)}")
+ send(owner, {:http_task_resp, ref, error})
+ end
+
+ defp endpoint(%URI{} = uri) do
+ uri |> to_string() |> String.to_charlist
+ end
+
+ defp headers do
+ [@user_agent]
+ end
+
+end
diff --git a/lib/exping/supervisor.ex b/lib/exping/supervisor.ex
index 403f35c..faf6ccb 100644
--- a/lib/exping/supervisor.ex
+++ b/lib/exping/supervisor.ex
@@ -13,6 +13,7 @@ defmodule ExPing.Supervisor do
@spec init(any) :: no_return
def init(_) do
children = [
+ supervisor(Task.Supervisor, [[name: ExPing.Supervisor.Task]])
]
supervise(children, strategy: :one_for_one, name: __MODULE__)
diff --git a/test/http_test.exs b/test/http_test.exs
new file mode 100644
index 0000000..61847d4
--- /dev/null
+++ b/test/http_test.exs
@@ -0,0 +1,36 @@
+defmodule ExPing.HTTP.Test do
+ use ExUnit.Case
+
+ describe "head requests" do
+ test "can get ok response" do
+ for code <- [200, 405] do
+ {:ok, _} = ExPing.HTTP.head(URI.parse("localhost/#{code}"))
+ end
+ end
+
+ test "can get error response" do
+ {:error, _} = ExPing.HTTP.head(URI.parse("localhost/500"))
+ end
+
+ test "can get timeout response" do
+ {:error, :timeout} = ExPing.HTTP.head(URI.parse("localhost/timeout"))
+ end
+ end
+
+ describe "get requests" do
+ test "can get ok response" do
+ for code <- [200, 400, 404] do
+ {:ok, _} = ExPing.HTTP.get(URI.parse("localhost/#{code}"))
+ end
+ end
+
+ test "can get error response" do
+ {:error, _} = ExPing.HTTP.get(URI.parse("localhost/500"))
+ end
+
+ test "can get timeout response" do
+ {:error, :timeout} = ExPing.HTTP.get(URI.parse("localhost/timeout"))
+ end
+ end
+
+end
diff --git a/test/support/test_http_client.ex b/test/support/test_http_client.ex
new file mode 100644
index 0000000..24ca509
--- /dev/null
+++ b/test/support/test_http_client.ex
@@ -0,0 +1,48 @@
+defmodule ExPing.Test.HTTPClient do
+ @moduledoc """
+ Mock HTTP client provides interface similar to `:httpc`.
+ """
+
+ def request(url), do: request(:get, {url, []}, [], [])
+ def request(:head, {url, _}, _, _) do
+ cond do
+ String.contains?(to_string(url), "/200") ->
+ {:ok, {status_line(200), [], []}}
+ String.contains?(to_string(url), "/405") ->
+ {:ok, {status_line(405), [], []}}
+ String.contains?(to_string(url), "/500") ->
+ Process.exit(self, :kill)
+ String.contains?(to_string(url), "/timeout") ->
+ :timer.sleep(20)
+ {:ok, {status_line(500), [], []}}
+ true ->
+ {:ok, {status_line(404), [], []}}
+ end
+ end
+ def request(:get, {url, _}, _, _) do
+ cond do
+ String.contains?(to_string(url), "/200") ->
+ {:ok, {status_line(200), [], []}}
+ String.contains?(to_string(url), "/400") ->
+ {:ok, {status_line(400), [], []}}
+ String.contains?(to_string(url), "/500") ->
+ Process.exit(self, :kill)
+ String.contains?(to_string(url), "/timeout") ->
+ :timer.sleep(20)
+ {:ok, {status_line(500), [], []}}
+ true ->
+ {:ok, {status_line(404), [], []}}
+ end
+ {:ok, {status_line(404), [], []}}
+ end
+
+ for {code, reason} <- [{200, 'OK'},
+ {400, 'Bad Request'},
+ {404, 'Not Found'},
+ {405, 'Method Not Allowed'},
+ {500, 'Internal Server Error'}] do
+ defp status_line(unquote(code)) do
+ {'HTTP/1.1', unquote(code), unquote(reason)}
+ end
+ end
+end