ets.ex (2375B)
1 defmodule Plug.Session.ETS do 2 @moduledoc """ 3 Stores the session in an in-memory ETS table. 4 5 This store does not create the ETS table; it expects that an 6 existing named table with public properties is passed as an 7 argument. 8 9 We don't recommend using this store in production as every 10 session will be stored in ETS and never cleaned until you 11 create a task responsible for cleaning up old entries. 12 13 Also, since the store is in-memory, it means sessions are 14 not shared between servers. If you deploy to more than one 15 machine, using this store is again not recommended. 16 17 This store, however, can be used as an example for creating 18 custom storages, based on Redis, Memcached, or a database 19 itself. 20 21 ## Options 22 23 * `:table` - ETS table name (required) 24 25 For more information on ETS tables, visit the Erlang documentation at 26 http://www.erlang.org/doc/man/ets.html. 27 28 ## Storage 29 30 The data is stored in ETS in the following format: 31 32 {sid :: String.t, data :: map, timestamp :: :erlang.timestamp} 33 34 The timestamp is updated whenever there is a read or write to the 35 table and it may be used to detect if a session is still active. 36 37 ## Examples 38 39 # Create an ETS table when the application starts 40 :ets.new(:session, [:named_table, :public, read_concurrency: true]) 41 42 # Use the session plug with the table name 43 plug Plug.Session, store: :ets, key: "sid", table: :session 44 45 """ 46 47 @behaviour Plug.Session.Store 48 49 @max_tries 100 50 51 @impl true 52 def init(opts) do 53 Keyword.fetch!(opts, :table) 54 end 55 56 @impl true 57 def get(_conn, sid, table) do 58 case :ets.lookup(table, sid) do 59 [{^sid, data, _timestamp}] -> 60 :ets.update_element(table, sid, {3, now()}) 61 {sid, data} 62 63 [] -> 64 {nil, %{}} 65 end 66 end 67 68 @impl true 69 def put(_conn, nil, data, table) do 70 put_new(data, table) 71 end 72 73 def put(_conn, sid, data, table) do 74 :ets.insert(table, {sid, data, now()}) 75 sid 76 end 77 78 @impl true 79 def delete(_conn, sid, table) do 80 :ets.delete(table, sid) 81 :ok 82 end 83 84 defp put_new(data, table, counter \\ 0) 85 when counter < @max_tries do 86 sid = Base.encode64(:crypto.strong_rand_bytes(96)) 87 88 if :ets.insert_new(table, {sid, data, now()}) do 89 sid 90 else 91 put_new(data, table, counter + 1) 92 end 93 end 94 95 defp now() do 96 :os.timestamp() 97 end 98 end