-
-
Save geofflane/67aa78675a88effa2ca03fb096fc45ee to your computer and use it in GitHub Desktop.
@doc """ | |
Struct to hold the configurable portions of HTTPoision | |
""" | |
defmodule HTTPoison.Client do | |
defstruct process_url: &__MODULE__.process_url/1, | |
process_request_body: &__MODULE__.process_request_body/1, | |
process_request_headers: &__MODULE__.process_request_headers/1, | |
process_request_options: &__MODULE__.process_request_options/1, | |
process_response_body: &__MODULE__.process_response_body/1, | |
process_headers: &__MODULE__.process_headers/1, | |
process_status_code: &__MODULE__.process_status_code/1 | |
def process_url(url), do: url | |
def process_request_body(body), do: body | |
def process_request_headers(headers), do: headers | |
def process_request_options(opts), do: opts | |
def process_response_body(body), do: body | |
def process_headers(headers), do: headers | |
def process_status_code(status_code), do: status_code | |
end | |
@doc """ | |
API that takes the client and applies the configuration | |
""" | |
defmodule HTTPoison.API do | |
def post(client, url, body, headers \\ [], options \\ []) do | |
request(client, :post, url, body, headers, options) | |
end | |
def get(client, url, headers \\ [], options \\ []) do | |
request(client, :get, url, "", headers, options) | |
end | |
def delete(client, url, headers \\ [], options \\ []) do | |
request(client, :delete, url, "", headers, options) | |
end | |
# Make the actual request | |
# This uses an undocumented function in HTTPoision which is probably a bit sketchy | |
defp request(client, method, url, body, headers, options) do | |
url = | |
if Keyword.has_key?(options, :params) do | |
url <> "?" <> URI.encode_query(options[:params]) | |
else | |
url | |
end | |
url = client.process_url.(to_string(url)) | |
body = client.process_request_body.(body) | |
headers = client.process_request_headers.(headers) | |
options = client.process_request_options.(options) | |
HTTPoison.Base.request(__MODULE__, method, url, body, headers, options, client.process_status_code, client.process_headers, client.process_response_body) | |
end | |
end | |
# How you would use it in your own code | |
@doc """ | |
client = Example.Pulsar.client(:service) | |
HTTPosion.API.get(client, "/foo") | |
""" | |
defmodule Example.Pulsar do | |
require Logger | |
def client(key) do | |
%HTTPoison.Client{process_url: process_url(key), | |
process_request_body: &json_encode/1, | |
process_response_body: &json_decode/1, | |
process_request_headers: &ensure_json_header/1, | |
process_request_options: &increase_timeouts/1, | |
} | |
end | |
def process_url(key) do | |
endpoint = endpoint(key) | |
fn (url) -> | |
endpoint <> url | |
end | |
end | |
def json_encode(body) do | |
Logger.debug("Connection body: #{inspect body}") | |
body | |
|> Poison.encode!() | |
end | |
def json_decode(body) do | |
Logger.debug("Connection response: #{inspect body}") | |
case Poison.decode(body) do | |
{:ok, response} -> response | |
error -> error | |
end | |
end | |
def ensure_json_header(headers) do | |
Enum.into(headers, [{"Content-Type", "application/json"}]) | |
end | |
def increase_timeouts(opts) do | |
opts | |
|> PffLive.Pulsar.default_options(:timeout, 10000) | |
|> PffLive.Pulsar.default_options(:recv_timeout, 30000) | |
end | |
defp config(key) do | |
Application.fetch_env!(:example, key) | |
end | |
defp endpoint(key) do | |
config(key) | |
|> Keyword.get(:pulsar_endpoint) | |
end | |
def default_options(options, keyword, default) do | |
val = Keyword.get(options, keyword) | |
if val do | |
options | |
else | |
[{keyword, default} | options] | |
end | |
end | |
end | |
There are multiple ways to achieve this with HTTPoison
. You could redefine request/5 and receive the endpoint that you intend as an option. Then the "base url" will be defined at that stage.
I also experimented a bit with a Behaviour
: https://github.com/edgurgel/httpehaviour
The behavior is interesting but doesn't seem to achieve what I want to do. Maybe a more generic idea:
How could someone create a generic JSON client that set the proper headers, transformed the body on the request and response, etc. but only change the endpoint so that they had an instance that they could use to call ServiceA and a different instance they could use to call ServiceB? That's the fundamental problem I was trying to solve. The only way I could see to do that was to turn the "client" into data instead of having it hardcoded in a module.
One other way is to pass an option which is always the last argument. Then build the url with the option passed?
Creates a functional interface that doesn't rely on a module macro for defining how you process the request. This allows for more easily sharing common functionality without having to duplicate the overrides of how you are processing the request and response.