Packages
livebook
0.14.6
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