Packages
ex_money
6.1.1
6.1.1
6.1.0
6.0.0
6.0.0-rc.0
5.24.2
5.24.1
5.24.0
retired
5.23.0
5.22.0
5.21.0
5.20.0
5.19.2
5.19.1
5.19.0
5.18.0
5.17.2
5.17.1
5.17.0
5.16.1
5.16.0
5.15.4
5.15.3
5.15.2
5.15.1
5.15.0
5.14.1
5.13.0
5.12.4
5.12.3
5.12.2
5.12.1
5.12.0
5.11.0
5.10.0
5.9.0
5.8.0
5.7.4
5.7.3
5.7.2
5.7.1
5.7.0
5.6.0
5.5.5
5.5.4
5.5.3
5.5.2
5.5.1
5.5.0
5.4.1
5.4.0
5.4.0-rc.0
5.3.2
5.3.1
5.3.0
5.2.1
5.2.0
5.1.0
5.0.2
5.0.1
5.0.0
retired
4.4.3
4.4.2
retired
4.4.1
4.4.0
4.3.0
4.2.2
4.2.1
4.2.0
4.1.0
4.0.0
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.1
3.3.0
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.0
3.0.0
2.12.2
2.12.1
retired
2.12.0
retired
2.11.0
2.10.0
2.9.1
2.9.0
2.8.0
2.7.3
2.7.2
2.7.1
2.7.0
2.6.1
2.6.0
2.5.0
2.4.0
2.3.0
2.2.2
2.2.1
2.2.0
2.1.0
2.1.0-rc.1
retired
2.1.0-rc.0
retired
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.1.3
1.1.2
1.1.1
retired
1.1.0
1.0.0
1.0.0-rc.0
retired
0.9.0
retired
0.8.5
retired
0.8.4
retired
0.8.3
retired
0.8.2
retired
0.8.1
retired
0.8.0
retired
0.7.0
retired
0.6.2
retired
0.6.1
retired
0.6.0
retired
0.5.3
retired
0.5.2
retired
0.5.1
retired
0.5.0
retired
0.4.3
retired
0.4.2
retired
0.4.1
retired
0.4.0
retired
0.3.0
retired
0.2.0
retired
0.1.7
retired
0.1.6
retired
0.1.5
retired
0.1.4
retired
0.1.3
retired
0.1.2
retired
0.1.1
retired
0.1.0
retired
0.0.16
retired
0.0.15
retired
0.0.14
retired
0.0.13
retired
0.0.12
retired
0.0.11
retired
0.0.10
retired
0.0.9
retired
0.0.7
retired
0.0.6
retired
0.0.5
retired
0.0.4
retired
0.0.3
retired
0.0.2
retired
Money functions for operations on and localization of a money data type with support for ISO 4217 currencies and ISO 24165 digial tokens (crypto currencies).
Current section
Files
Jump to
Current section
Files
lib/money/exchange_rates/http.ex
defmodule Money.ExchangeRates.HTTP do
@moduledoc false
@doc """
Performs an HTTP GET request, automatically attaching conditional request headers
(`if-none-match`, `if-modified-since`) from a node-wide ETag cache keyed by URL.
Persists response headers back into the cache after each request.
Returns:
* `{:ok, body}` — the request succeeded and `body` contains the response body
* `{:ok, :not_modified}` — the server returned 304; the cached response is still valid
* `{:error, {Money.ExchangeRateError, reason}}` — the request failed
"""
@spec get(url :: binary, opts :: keyword) ::
{:ok, term} | {:ok, :not_modified} | {:error, {Money.ExchangeRateError, binary}}
def get(url, opts \\ []) do
cache = init_cache()
http_client = Money.get_env(:exchange_rates_http_client, Localize.Utils.Http, :module)
case http_client.get_with_headers({url, build_headers(cache, url)}, opts) do
{:ok, headers, body} ->
save_headers(cache, url, headers)
{:ok, body}
{:not_modified, headers} ->
save_headers(cache, url, headers)
{:ok, :not_modified}
{:error, reason} ->
{:error, {Money.ExchangeRateError, "#{inspect(reason)}"}}
end
end
# The ETag cache is a node-wide named table shared by all retrievers. Because
# multiple named retrievers may call `get/2` concurrently, creation must be
# race-safe: two processes can both observe `:undefined` from `:ets.whereis/1`
# and then race on `:ets.new/2`, where the loser would raise `ArgumentError`
# for a duplicate named table. Rescue that case and return the existing table.
defp init_cache do
case :ets.whereis(:etag_cache) do
:undefined ->
try do
:ets.new(:etag_cache, [:named_table, :public])
rescue
ArgumentError -> :etag_cache
end
_table ->
:etag_cache
end
end
defp build_headers(cache, url) do
etag = :ets.lookup_element(cache, url, 2, nil)
last_modified = :ets.lookup_element(cache, url, 3, nil)
Enum.reject(
[{~c"if-none-match", etag}, {~c"if-modified-since", last_modified}],
&is_nil(elem(&1, 1))
)
end
defp save_headers(cache, url, headers) do
etag = :proplists.get_value(~c"etag", headers, nil)
last_modified = :proplists.get_value(~c"last-modified", headers, nil)
:ets.insert(cache, {url, etag, last_modified})
end
end