Skip to content

Commit

Permalink
Implement first version of Redis storage
Browse files Browse the repository at this point in the history
  • Loading branch information
drapergeek committed Jan 20, 2017
1 parent 5529ec9 commit becbd4c
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 53 deletions.
29 changes: 0 additions & 29 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,30 +1 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.

# You can configure for your application as:
#
# config :redbird, key: :value
#
# And access this configuration in your application as:
#
# Application.get_env(:redbird, :key)
#
# Or configure a 3rd-party app:
#
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
12 changes: 12 additions & 0 deletions lib/redbird.ex
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
defmodule Redbird do
use Application

def start(_type, _args) do
import Supervisor.Spec

children = [
worker(Redbird.Redis, [Redbird.Redis.pid()]),
]

opts = [strategy: :one_for_one, name: Redbird.Supervisor]
Supervisor.start_link(children, opts)
end
end
47 changes: 47 additions & 0 deletions lib/redbird/plug/session/redis.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
defmodule Plug.Session.REDIS do
import Redbird.Redis
require IEx

@moduledoc """
Stores the session in a redis store.
"""

@behaviour Plug.Session.Store

@max_session_time 86_164 * 30

def init(opts) do
opts
end

def get(_conn, redis_key, _init_options) do
case get(redis_key) do
:undefined -> {nil, %{}}
value -> {redis_key, value |> :erlang.binary_to_term}
end
end

def put(conn, nil, data, init_options) do
put(conn, generate_random_key(), data, init_options)
end
def put(_conn, redis_key, data, init_options) do
setex(%{key: redis_key, value: data, seconds: session_expiration(init_options)})
redis_key
end

def delete(_conn, redis_key, _kinit_options) do
del(redis_key)
:ok
end

defp generate_random_key do
:crypto.strong_rand_bytes(96) |> Base.encode64
end

defp session_expiration(opts) do
case opts[:expiration_in_seconds] do
seconds when is_integer(seconds) -> seconds
_ -> @max_session_time
end
end
end
23 changes: 23 additions & 0 deletions lib/redis.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule Redbird.Redis do
def start_link(name) do
{:ok, client} = Exredis.start_link
true = Process.register(client, name)
{:ok, client}
end

def get(value) do
Exredis.Api.get(pid(), value)
end

def setex(%{key: key, value: value, seconds: seconds}) do
Exredis.Api.setex(pid(), key, seconds, value)
end

def del(key) do
Exredis.Api.del(pid(), key)
end

def pid do
:redbird_phoenix_session
end
end
42 changes: 22 additions & 20 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,33 @@ defmodule Redbird.Mixfile do
use Mix.Project

def project do
[app: :redbird,
version: "0.1.0",
elixir: "~> 1.3",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps()]
[
app: :redbird,
build_embedded: Mix.env == :prod,
deps: deps(),
elixir: "~> 1.3",
start_permanent: Mix.env == :prod,
version: "0.1.0",
package: [
maintainers: ["anellis", "drapergeek"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/thoughtbot/redbird"}
],
]
end

# Configuration for the OTP application
#
# Type "mix help compile.app" for more information
def application do
[applications: [:logger]]
[
applications: [:logger],
mod: {Redbird, []},
]
end

# Dependencies can be Hex packages:
#
# {:mydep, "~> 0.3.0"}
#
# Or git/path repositories:
#
# {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
#
# Type "mix help deps" for more examples and options
defp deps do
[]
[
{:ex_doc, "~> 0.13", only: :dev},
{:exredis, "~> 0.2"},
{:plug, "~> 1.1"},
]
end
end
6 changes: 6 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
%{"earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], []},
"eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []},
"ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
"exredis": {:hex, :exredis, "0.2.5", "34d02fa9ef32174cbd790b166746f91b3dbc131ce37a2d9a2451e8bba164b423", [:mix], [{:eredis, ">= 1.0.8", [hex: :eredis, optional: false]}]},
"mime": {:hex, :mime, "1.0.1", "05c393850524767d13a53627df71beeebb016205eb43bfbd92d14d24ec7a1b51", [:mix], []},
"plug": {:hex, :plug, "1.3.0", "6e2b01afc5db3fd011ca4a16efd9cb424528c157c30a44a0186bcc92c7b2e8f3", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]}}
93 changes: 89 additions & 4 deletions test/redbird_test.exs
Original file line number Diff line number Diff line change
@@ -1,8 +1,93 @@
defmodule RedbirdTest do
use ExUnit.Case
doctest Redbird
use ExUnit.Case, async: true
use Plug.Test
alias Plug.Session.REDIS

test "the truth" do
assert 1 + 1 == 2
@default_opts [
store: :redis,
key: "_session_key",
]
@secret String.duplicate("thoughtbot", 8)

defp sign_plug(options) do
options =
options ++ @default_opts
|> Keyword.put(:encrypt, false)
Plug.Session.init(options)
end

defp sign_conn(conn, options \\ []) do
put_in(conn.secret_key_base, @secret)
|> Plug.Session.call(sign_plug(options))
|> fetch_session
end

setup_all do
Application.stop(:redbird)
:ok = Application.start(:redbird)
end

describe "get" do
test "when there is value stored it is retrieved" do
conn = conn(:get, "/")
|> sign_conn
|> put_session(:foo, "bar")
|> send_resp(200, "")

conn =
conn(:get, "/")
|> recycle_cookies(conn)
|> sign_conn
|> send_resp(200, "")

assert conn |> get_session(:foo) == "bar"
end

test "when there is no session with the key, it returns {:nil, %{}}" do
key = "redis_session"
conn = %{}
options = []

assert {nil, %{}} = REDIS.get(conn, key, options)
end
end

describe "put" do
test "it sets the session properly" do
conn = conn(:get, "/")
|> sign_conn
|> put_session(:foo, "bar")
|> send_resp(200, "")
assert conn |> get_session(:foo) == "bar"
end

test "it allows configuring session expiration" do
conn = conn(:get, "/")
|> sign_conn(expiration_in_seconds: 1)
|> put_session(:foo, "bar")
|> send_resp(200, "")

:timer.sleep(1000)

conn =
conn(:get, "/")
|> recycle_cookies(conn)
|> sign_conn
|> send_resp(200, "")

assert conn |> get_session(:foo) |> is_nil
end
end

describe "delete" do
test "delete session" do
key = "redis_session"
conn = %{}
options = []
REDIS.put(conn, key, %{foo: :bar}, options)
REDIS.delete(conn, key, options)

assert {nil, %{}} = REDIS.get(conn, key, options)
end
end
end

0 comments on commit becbd4c

Please sign in to comment.