Current section
Files
Jump to
Current section
Files
lib/maxwell/middleware/cache.ex
defmodule Maxwell.Middleware.Cache do
use Maxwell.Middleware
alias Maxwell.Conn
@default_namespace "default"
@pri_from_cache :MIDDLE_CACHE_from_cache
@pri_from_source :MIDDLE_CACHE_from_source
@pri_ttl :MIDDLE_CACHE_ttl
def init(opts) do
namespace = opts[:namespace] || @default_namespace
hash_func = opts[:hash_func] || &__MODULE__.default_hash_func/1
ttl = opts[:ttl]
{namespace, hash_func, ttl}
end
@doc """
Make this requrest get response from cache first.
"""
def from_cache(%Maxwell.Conn{} = conn) do
Maxwell.Conn.put_private(conn, @pri_from_cache, true)
end
@doc """
Make this requrest get response from remote server and set cache.
"""
def from_source(%Maxwell.Conn{} = conn) do
Maxwell.Conn.put_private(conn, @pri_from_source, true)
end
@doc """
Set ttl to cache the request.
"""
def set_ttl(%Maxwell.Conn{} = conn, ttl) do
Maxwell.Conn.put_private(conn, @pri_ttl, ttl)
end
@doc """
Default hash function. Generate a key from `conn` to get/store response from/to cache.
Can be replaced by user defined function by pass `:hash_func` to `init/1`.
"""
def default_hash_func(%Maxwell.Conn{} = conn) do
query_string = conn.query_string |> Maxwell.Query.encode
headers = conn.req_headers |> Maxwell.Query.encode
body = conn.req_body |> :erlang.phash2
"#{conn.method}|#{conn.url}|#{conn.path}|#{query_string}|#{headers}|#{body}"
end
def call(conn = %Maxwell.Conn{private: %{@pri_from_cache => true}}, next, {namespace, hash_func, ttl}) do
ttl = Map.get(conn.private, @pri_ttl, ttl)
key = pk(conn, namespace, hash_func)
case Cachex.get(:maxwell_cache_cachex, key) do
{:ok, {resp_body, resp_headers}} ->
%{conn | status: 200,
resp_headers: resp_headers,
resp_body: resp_body,
state: :sent,
req_body: nil}
_ ->
conn
|> next.()
|> set_cache(key, ttl)
end
end
def call(conn = %Maxwell.Conn{private: %{@pri_from_source => true}}, next, {namespace, hash_func, ttl}) do
ttl = Map.get(conn.private, @pri_ttl, ttl)
key = pk(conn, namespace, hash_func)
conn
|> next.()
|> set_cache(key, ttl)
end
def call(conn = %Maxwell.Conn{}, next, _opts), do: next.(conn)
defp pk(conn, namespace, hash_func), do: namespace <> "#" <> hash_func.(conn)
defp set_cache(conn=%Maxwell.Conn{status: status}, key, ttl) when status == 200 do
value = {conn.resp_body, conn.resp_headers}
ret = Cachex.set(:maxwell_cache_cachex, key, value, ttl: ttl, async: true)
case ret do
{:ok, true} -> conn
_ -> {:error, :set_cache_error, conn}
end
end
defp set_cache(conn, _key, _ttl), do: conn
end