Packages
livebook
0.14.0-rc.1
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook_web/plugs/proxy_plug.ex
defmodule LivebookWeb.ProxyPlug do
@behaviour Plug
import Plug.Conn
alias LivebookWeb.NotFoundError
@impl true
def init(opts), do: opts
# We group all routes under the /proxy namespace, so that it's easy
# to expose them when Livebook runs behind an authentication proxy.
# The users may want to expose them, for example, to use as webhooks
@impl true
def call(%{path_info: ["proxy", "sessions", id | path_info]} = conn, _opts) do
session = fetch_session!(id)
Livebook.Session.reset_auto_shutdown(session.pid)
proxy_handler_spec = fetch_proxy_handler_spec!(session)
conn = prepare_conn(conn, path_info, ["proxy", "sessions", id])
call_proxy_handler(proxy_handler_spec, conn)
end
def call(%{path_info: ["proxy", "apps", slug, "sessions", id | path_info]} = conn, _opts) do
app = fetch_app!(slug)
unless Enum.any?(app.sessions, &(&1.id == id)) do
raise NotFoundError, "could not find an app session with id #{inspect(id)}"
end
session = fetch_session!(id)
Livebook.Session.reset_auto_shutdown(session.pid)
await_app_session_ready(app, session.id)
proxy_handler_spec = fetch_proxy_handler_spec!(session)
conn = prepare_conn(conn, path_info, ["proxy", "apps", slug, "sessions", id])
call_proxy_handler(proxy_handler_spec, conn)
end
def call(%{path_info: ["proxy", "apps", slug | path_info]} = conn, _opts) do
app = fetch_app!(slug)
if app.multi_session do
raise LivebookWeb.BadRequestError,
"the requested app is multi-session. In order to send requests to this app," <>
" you need to start a session and use its specific URL"
end
session_id = Livebook.App.get_session_id(app.pid)
{:ok, session} = Livebook.Sessions.fetch_session(session_id)
Livebook.Session.reset_auto_shutdown(session.pid)
await_app_session_ready(app, session.id)
proxy_handler_spec = fetch_proxy_handler_spec!(session)
conn = prepare_conn(conn, path_info, ["proxy", "apps", slug])
call_proxy_handler(proxy_handler_spec, conn)
end
def call(conn, _opts) do
conn
end
defp fetch_app!(slug) do
case Livebook.Apps.fetch_app(slug) do
{:ok, app} -> app
:error -> raise NotFoundError, "could not find an app with slug #{inspect(slug)}"
end
end
defp fetch_session!(id) do
case Livebook.Sessions.fetch_session(id) do
{:ok, session} -> session
{:error, _} -> raise NotFoundError, "could not find a session with id #{id}"
end
end
defp fetch_proxy_handler_spec!(session) do
case Livebook.Session.fetch_proxy_handler_spec(session.pid) do
{:ok, pid} ->
pid
{:error, _} ->
raise NotFoundError,
"the session does not listen to proxied requests." <>
" See the Kino.Proxy documentation to learn about defining" <>
" custom request handlers"
end
end
defp prepare_conn(conn, path_info, script_name) do
%{conn | path_info: path_info, script_name: conn.script_name ++ script_name}
end
defp call_proxy_handler(proxy_handler_spec, conn) do
{module, function, args} = proxy_handler_spec
conn = apply(module, function, args ++ [conn])
halt(conn)
end
defp await_app_session_ready(app, session_id) do
unless session_ready?(app, session_id) do
Livebook.App.subscribe(app.slug)
# We fetch the app again, in case it had changed before we subscribed
app = Livebook.App.get_by_pid(app.pid)
await_session_execution_loop(app, session_id)
Livebook.App.unsubscribe(app.slug)
end
end
defp await_session_execution_loop(%{slug: slug} = app, session_id) do
unless session_ready?(app, session_id) do
receive do
{:app_updated, %{slug: ^slug} = app} ->
await_session_execution_loop(app, session_id)
end
end
end
defp session_ready?(app, session_id) do
if session = Enum.find(app.sessions, &(&1.id == session_id)) do
session.app_status.execution != :executing
else
false
end
end
end