Skip to content

Instantly share code, notes, and snippets.

@bluzky
Last active May 18, 2018 01:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bluzky/3e6d628eadcc32a6793470ce2775c14c to your computer and use it in GitHub Desktop.
Save bluzky/3e6d628eadcc32a6793470ce2775c14c to your computer and use it in GitHub Desktop.
Simple ETS cache for Phoenix
defmodule PhoenixCache.Bucket do
use GenServer
alias :ets, as: Ets
@expired_after 6 * 60
def start_link(args \\ []) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
def set(key, value) do
GenServer.cast(__MODULE__, {:set, key, value})
end
@doc """
Custom TTL for cache entry
ttl: Time to live in second
"""
def set(key, value, ttl) do
GenServer.cast(__MODULE__, {:set, key, value, ttl})
end
def get(key) do
GenServer.call(__MODULE__, {:get, key})
end
def delete(key) do
GenServer.cast(__MODULE__, {:delete, key})
end
# Server callbacks
# Server (callbacks)
@impl true
def init(state) do
Ets.new(:simple_cache, [:set, :protected, :named_table])
{:ok, state}
end
def handle_call({:get, key}, _from, state) do
rs = Ets.lookup(:simple_cache, key) |> List.first()
if rs == nil do
{:reply, {:error, :not_found}, state}
else
expired_at = elem(rs, 2)
cond do
NaiveDateTime.diff(NaiveDateTime.utc_now(), expired_at) > 0 ->
{:reply, {:error, :expired}, state}
true ->
{:reply, {:ok, elem(rs, 1)}, state}
end
end
end
@doc """
Default TTL
"""
def handle_cast({:set, key, val}, state) do
expired_at =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(@expired_after, :second)
Ets.insert(:simple_cache, {key, val, expired_at})
{:noreply, state}
end
@doc """
Custom TTL
"""
def handle_cast({:set, key, val, ttl}, state) do
inserted_at =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(ttl, :second)
Ets.insert(:simple_cache, {key, val, inserted_at})
{:noreply, state}
end
@impl true
def handle_cast({:delete, key}, state) do
Ets.delete(:simple_cache, key)
{:noreply, state}
end
end
defmodule PhoenixCache.Plug.Cache doimport Plug.Conn
# 6 minute
@default_ttl 6 * 60
def init(ttl \\ nil), do: ttl
def call(conn, ttl \\ nil) do
ttl = ttl || @default_ttl
# Chỉ cache với GET requestif conn.method == "GET" do# tạo key từ request path và query param, thông thường# thì cùng path và cùng param thì kết quả là giống nhau
key = "#{conn.request_path}-#{conn.query_string}"
case PhoenixCache.Bucket.get(key) do
{:ok, body} ->
IO.puts("PLUG HIT")
# nếu đã cache thì trả về ngay
conn
|> send_resp(200, body)
|> halt
_ ->
IO.puts("PLUG MISS")
# nếu chưa cache thì xử lý như bình thường
conn
|> assign(:ttl, ttl)
|> register_before_send(&cache_before_send/1) # gọi hàm này trước khi trả vềendelse
conn
endend
def cache_before_send(conn) do# nếu request đuợc xử lý thành công thì cacheif conn.status == 200 dokey = "#{conn.request_path}-#{conn.query_string}"data = conn.resp_body
PhoenixCache.Bucket.set(key, data, conn.assigns[:ttl] || @default_ttl)
conn
else# không thì kệ chúng mày
conn
endend
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment