Packages
rambla
1.0.1
1.5.0
1.4.2
1.4.1
1.4.0
1.3.0
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.1
1.0.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.0
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.2
0.12.1
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Easy publishing to many different targets. Supported back-ends: - Rabbit [Amqp](https://hexdocs.pm/amqp/) - Redis [Redix](https://hexdocs.pm/redix) - Http [:httpc](http://erlang.org/doc/man/httpc.html) - Smtp [:gen_smtp](https://hexdocs.pm/gen_smtp) - Slack [Envío](https://hexdocs.pm/envio)
Current section
Files
Jump to
Current section
Files
lib/rambla/connections/http.ex
defmodule Rambla.Http do
@moduledoc """
Default connection implementation for 🕸️ HTTP.
It expects a message to be a map, containing the following fields:
`:method`, `:path`, `:query`, `:body` _and_ the optional `:type`
that otherwise would be inferred from the body type.
For instance, this call would send a POST request with a JSON specified as body.
```elixir
Rambla.publish(
Rambla.Http,
%{method: :post, body: %{message: "I ❤ HTTP"}}
}
```
If the second argument `message` is `binary()` it’s treated as an URL _and_
`:get` is implied.
---
List of all possible options might be found in
[`:httpc.request/4`](http://erlang.org/doc/man/httpc.html#request-4), names are preserved.
"""
require Logger
@behaviour Rambla.Connection
@conn_params ~w|host port|a
@impl Rambla.Connection
def connect(params) when is_list(params) do
if is_nil(params[:host]),
do:
raise(Rambla.Exceptions.Connection,
value: params,
source: __MODULE__,
reason: "inconsistent params",
expected: "🕸️ configuration with :host key"
)
[defaults, opts] =
params
|> Keyword.split(@conn_params)
|> Tuple.to_list()
|> Enum.map(&Map.new/1)
%Rambla.Connection{
conn: %Rambla.Connection.Config{conn: params[:host], opts: opts, defaults: defaults},
conn_type: __MODULE__,
conn_pid: self(),
conn_params: params,
errors: []
}
end
@impl Rambla.Connection
def publish(%Rambla.Connection.Config{} = conn, message) when is_binary(message),
do: publish(conn, Jason.decode!(message))
@impl Rambla.Connection
def publish(%Rambla.Connection.Config{} = conn, message) when is_list(message),
do: publish(conn, Map.new(message))
@impl Rambla.Connection
def publish(%Rambla.Connection.Config{opts: opts, defaults: defaults}, message)
when is_map(opts) and is_map(message) do
{method, message} = Map.pop(message, :method, :get)
{host, message} = Map.pop(message, :host, Map.get(defaults, :host))
{port, message} = Map.pop(message, :port, Map.get(defaults, :port))
{headers, message} = Map.pop(message, :headers, Map.get(defaults, :headers, []))
{content_type, message} =
Map.pop(message, :content_type, Map.get(defaults, :content_type, ~c"application/json"))
{path, message} = Map.pop(message, :path, Map.get(opts, :path, Map.get(defaults, :path, "")))
{http_options, message} = Map.pop(message, :http_options, Map.get(opts, :http_options, []))
{options, message} = Map.pop(message, :options, Map.get(opts, :options, []))
{%{} = query, message} = Map.pop(message, :query, Map.get(opts, :query, %{}))
{body, _message} = Map.pop(message, :body, Map.get(opts, :body, %{}))
host_port =
[host, port]
|> Enum.reject(&(to_string(&1) == ""))
|> Enum.join(":")
path_query =
[path, Plug.Conn.Query.encode(query)]
|> Enum.map(&String.trim(&1, "/"))
|> Enum.reject(&(&1 == ""))
|> Enum.join("?")
url =
[host_port, path_query]
|> Enum.reject(&(&1 == ""))
|> Enum.join("/")
headers = for {k, v} <- headers, do: {:erlang.binary_to_list(k), :erlang.binary_to_list(v)}
request(method, url, headers, body, http_options, options, content_type)
end
@typep method :: :head | :get | :put | :post | :trace | :options | :delete | :patch
@typep url :: binary()
@typep header :: {binary(), binary()}
@typep headers :: [header()]
@typep body :: charlist() | binary()
@typep option ::
{:sync, boolean()}
| {:stream, any()}
| {:body_format, any()}
| {:full_result, boolean()}
| {:headers_as_is, boolean()}
| {:socket_opts, any()}
| {:receiver, any()}
| {:ipv6_host_with_brackets, boolean()}
@typep options :: [option()]
@typep http_option ::
{:timeout, timeout()}
| {:connect_timeout, timeout()}
| {:ssl, any()}
| {:essl, any()}
| {:autoredirect, boolean()}
| {:proxy_auth, {charlist(), charlist()}}
| {:version, charlist()}
| {:relaxed, boolean()}
@typep http_options :: [http_option()]
@typep content_type :: charlist()
@typep status_line :: {charlist(), integer(), charlist()}
@spec request(
method :: method(),
url :: url(),
headers :: headers(),
body :: body(),
http_options :: http_options(),
options :: options(),
content_type :: content_type()
) :: {:ok, {status_line(), list()}} | {:error, any()}
defp request(
method,
url,
headers,
body,
http_options,
options,
content_type
)
Enum.each([:post, :put], fn m ->
defp request(unquote(m), url, headers, body, http_options, options, content_type) do
unquote(m)
|> :httpc.request(
{:erlang.binary_to_list(url), headers, content_type,
body |> Jason.encode!() |> :erlang.binary_to_list()},
http_options,
options
)
|> tap_log()
end
end)
Enum.each([:get, :head, :options, :delete], fn m ->
defp request(unquote(m), url, headers, _body, http_options, options, _content_type) do
unquote(m)
|> :httpc.request({:erlang.binary_to_list(url), headers}, http_options, options)
|> tap_log()
end
end)
defp tap_log({:ok, {{_, ok, _}, _, response}}) when ok in 200..299 do
Logger.debug("[🕸️] Response: " <> inspect(response))
{:ok, response}
end
defp tap_log({:ok, {{_, ko, _}, _, response}}) do
Logger.warning("[🕸️] Response: " <> inspect({ko, response}))
{:error, {ko, response}}
end
defp tap_log(ko) do
Logger.error("[🕸️] Error: " <> inspect(ko))
ko
end
end