diff --git a/README.md b/README.md index 233aef6..a540b14 100644 --- a/README.md +++ b/README.md @@ -30,5 +30,6 @@ These services may need to be configured in order to enable mu-cache support. Ch ## Debugging Debugging of cache keys is helped by following environment variables: + - `LOG_CACHE_CLEAR_EVENT`: Store cache clear events in the database - `LOG_CACHE_KEYS`: Logs received cache key to a response - `LOG_CLEAR_KEYS`: Logs received clear keys either as a response, or explicitly received through ./mu/delta. diff --git a/config/config.exs b/config/config.exs index 7e440ca..b777a4e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -21,6 +21,7 @@ end # 3rd-party users, it should be done in your "mix.exs" file. config :mu_cache, + log_cache_clear_event: CH.system_boolean("LOG_CACHE_CLEAR_EVENT"), log_cache_keys: CH.system_boolean("LOG_CACHE_KEYS"), log_clear_keys: CH.system_boolean("LOG_CLEAR_KEYS") diff --git a/lib/cache/cache.ex b/lib/cache/cache.ex index 201360e..7c2ad22 100644 --- a/lib/cache/cache.ex +++ b/lib/cache/cache.ex @@ -93,13 +93,18 @@ defmodule Cache.Registry do # We have multiple clear_keys and need to update the state for it. %{cache: cache, caches_by_key: caches_by_key} = state + # IO.inspect(clear_keys, label: "Clearing these keys") + # IO.inspect(cache, label: "Cache before clearing the keys") + cache = Enum.reduce(clear_keys, cache, fn clear_key, cache -> keys_to_remove = Map.get(caches_by_key, clear_key, []) + maybe_send_cleared_key_to_database!(keys_to_remove) cache = Map.drop(cache, keys_to_remove) cache end) + # IO.inspect(cache, label: "Cache after clearing the keys") caches_by_key = Map.drop(caches_by_key, clear_keys) %{state | cache: cache, caches_by_key: caches_by_key} @@ -135,4 +140,31 @@ defmodule Cache.Registry do %{state | cache: cache} end + + defp maybe_send_cleared_key_to_database!(request_key_to_remove) do + # Write to the database when a cache for a url is cleared + if Application.get_env(:mu_cache, :log_cache_clear_event) do + Enum.map(request_key_to_remove, fn req_key -> + {method, path, query, auth} = req_key + uuid = Support.generate_uuid() + auth_escaped = Support.sparql_escape(auth) + query = """ + PREFIX mu: + PREFIX mucache: + INSERT DATA { + GRAPH { + a mucache:CacheClear ; + mu:uuid "#{uuid}"; + mucache:path "#{path}"; + mucache:method "#{method}"; + mucache:query "#{query}"; + mucache:muAuthAllowedGroups \"\"\"#{auth_escaped}\"\"\"; + mucache:muAuthUsedGroups \"\"\"#{auth_escaped}\"\"\". + } + } + """ + Support.update(query) + end) + end + end end diff --git a/lib/manipulators/cache_key_logger.ex b/lib/manipulators/cache_key_logger.ex index cdc5e54..7f35965 100644 --- a/lib/manipulators/cache_key_logger.ex +++ b/lib/manipulators/cache_key_logger.ex @@ -38,7 +38,9 @@ defmodule Manipulators.CacheKeyLogger do end defp header_value(headers, header_name) do - header = Enum.find(headers, header_name) + header = + headers + |> Enum.find({nil, "[]"}, &match?({header_name, _}, &1)) if header do elem(header, 1) diff --git a/lib/support.ex b/lib/support.ex new file mode 100644 index 0000000..75e40fa --- /dev/null +++ b/lib/support.ex @@ -0,0 +1,63 @@ +# Note: this is copied from https://github.com/mu-semtech/mu-elixir-template should be replaced by integrating the template +defmodule Support do + # environment variables + def sparql_endpoint() do + System.get_env("MU_SPARQL_ENDPOINT") + end + def application_graph() do + System.get_env("MU_APPLICATION_GRAPH") + end + def sparql_timeout() do + System.get_env("MU_SPARQL_TIMEOUT") + end + def log_level() do + System.get_env("LOG_LEVEL") + end + + # uuid helper + def generate_uuid() do + UUID.uuid4() + end + + # graph helper + def graph() do + application_graph() + end + + # query helper + def _query_get_URL(escaped_query_string) do + if sparql_timeout() > 0 do + "#{sparql_endpoint()}?query=#{escaped_query_string}&timeout=#{sparql_timeout()}" + else + "#{sparql_endpoint()}?query=#{escaped_query_string}" + end + end + + def query(query_string) do + query_string + |> URI.encode + |> _query_get_URL + |> HTTPoison.get!([{"Accept", "application/json"}]) + |> ( &( &1.body ) ).() + |> Poison.decode! + end + + def update(query_string) do + HTTPoison.post!( + sparql_endpoint(), + [ + "query=" <> + URI.encode_www_form(query_string) <> + "&format=" <> URI.encode_www_form("application/sparql-results+json") + ], + ["Content-Type": "application/x-www-form-urlencoded"], + [] + ) + |> ( &( &1.body ) ).() + |> Poison.decode! + end + + def sparql_escape(value) do + String.replace(value, "\"", "\\\"") + end +end diff --git a/mix.exs b/mix.exs index 41e47b0..8103c71 100644 --- a/mix.exs +++ b/mix.exs @@ -48,7 +48,9 @@ defmodule UsePlugProxy.Mixfile do {:plug_cowboy, "~> 2.4.1"}, {:dialyxir, "~> 1.0", only: [:dev], runtime: false}, {:credo, "~> 1.5", only: [:dev, :test], runtime: false}, - {:poison, "~> 2.0"} + {:poison, "~> 2.0"}, + {:uuid, "~> 1.1"}, + {:httpoison, "~> 1.5"} ] end end