Phoenix - Tips
31 Mar 2018- (how to) make application available from outside
- using SSL
- umbrella apps vs. contexts
- (how to) stop children of application supervisor
- (how to) perform signature verification
(how to) make application available from outside
say, it might be useful to make application available on local network in development so that it can be tested by others.
# config/config.exs
config :billing, BillingWeb.Endpoint,
+ # bind application to local IP address
+ http: [ip: {192, 168, 0, 36}, port: 4000],
using SSL
- https://hexdocs.pm/phoenix/endpoint.html#using-ssl
- http://ohanhi.com/phoenix-ssl-localhost.html
- https://gist.github.com/tadast/9932075
the idea is that it’s possible to generate private key and self-signed
certificate and specify them in https
key of endpoint configuration.
umbrella apps vs. contexts
https://www.reddit.com/r/elixir/comments/6bpah8/phoenix_context_vs_elixir_umbrella_apps/dhprpl5/
The rule of thumb is to use umbrellas where you can use separate storage and contexts everywhere else. Many quite big apps use only one database. It doesn’t make sense to configure them separately in Umbrellas and you can’t stop one part without bringing entire system down. It doesn’t make sense to extract them to umbrellas, but you still don’t want to tangle stuff too much.
(how to) stop children of application supervisor
children will be automatically restarted by application supervisor => stop application supervisor and start required supervisors manually.
example
sometimes it might be necessary to put application into maintenance mode when the whole application is stopped but you still need access to DB.
the point is that you cannot stop child supervisor or worker - it will be restarted by application supervisor immediately:
iex> Supervisor.which_children(MyApp.Supervisor)
[
{MyApp.TaskSupervisor, #PID<0.482.0>, :supervisor, [Task.Supervisor]},
{MyAppWeb.Endpoint, #PID<0.468.0>, :supervisor, [MyAppWeb.Endpoint]},
{MyApp.Repo, #PID<0.435.0>, :supervisor, [MyApp.Repo]},
{MyApp.Scheduler, #PID<0.428.0>, :worker, [MyApp.Scheduler]}
]
iex> Supervisor.stop(:c.pid(0,428,0))
:ok
iex> Supervisor.which_children(MyApp.Supervisor)
[
{MyApp.TaskSupervisor, #PID<0.482.0>, :supervisor, [Task.Supervisor]},
{MyAppWeb.Endpoint, #PID<0.468.0>, :supervisor, [MyAppWeb.Endpoint]},
{MyApp.Repo, #PID<0.435.0>, :supervisor, [MyApp.Repo]},
{MyApp.Scheduler, #PID<0.1854.0>, :worker, [MyApp.Scheduler]}
]
=> stop application supervisor instead and start MyApp.Repo
supervisor
manually afterwards (this is what I do in test/test_helper.exs - start
required supervisors only without starting the whole application):
iex> Supervisor.stop(MyApp.Superrvisor)
:ok
[info] Application my_app exited: normal
iex> MyApp.Repo.start_link()
{:ok, #PID<0.486.0>}
UPDATE
unfortunately this method doesn’t work in production:
iex(my_app@127.0.0.1)2> Supervisor.stop(MyApp.Supervisor)
:ok
iex(my_app@127.0.0.1)3> *** ERROR: Shell process terminated! (^G to start new job) ***
=> I cannot start a new shell in production like in development.
(how to) perform signature verification
- https://www.userinterfacing.com/verifying-request-signatures-in-elixir-phoenix
- https://github.com/hamiltop/ashes/blob/master/lib/ashes/github_webhook_plug.ex
see Phoenix - Troubleshooting post on how to read request body in custom plug.
# lib/my_app_web/router.ex
defmodule MyAppWeb.Router do
use MyAppWeb, :router
pipeline :line do
plug MyAppWeb.Plugs.Line.SignatureVerification
end
end
# lib/my_app_web/plugs/line/signature_verification.ex
defmodule MyAppWeb.Plugs.Line.SignatureVerification do
import Plug.Conn
import Plug.Crypto, only: [secure_compare: 2]
@channel_secret Application.get_env(:my_app, :line)[:channel_secret]
def init(opts), do: opts
def call(conn, _opts) do
my_signature = signature(conn)
[line_signature] = get_req_header(conn, "x-line-signature")
if secure_compare(my_signature, line_signature) do
conn
else
unauthorized(conn)
end
end
defp signature(conn) do
body = MyAppWeb.CacheBodyReader.read_cached_body(conn)
:sha256
|> :crypto.hmac(@channel_secret, body)
|> Base.encode64()
end
defp unauthorized(conn) do
conn
|> send_resp(401, "Unauthorized")
|> halt()
end
end