-
Notifications
You must be signed in to change notification settings - Fork 437
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow Livebook to proxy requests to the runtime (#2608)
- Loading branch information
Showing
13 changed files
with
252 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
defmodule LivebookWeb.ErrorJSON do | ||
# By default, Phoenix returns the status message from | ||
# the template name. For example, "404.json" becomes | ||
# "Not Found". | ||
def render(template, _assigns) do | ||
%{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
defmodule LivebookWeb.ProxyPlug do | ||
@behaviour Plug | ||
import Plug.Conn | ||
|
||
alias LivebookWeb.NotFoundError | ||
|
||
@impl true | ||
def init(opts), do: opts | ||
|
||
@impl true | ||
def call(%{path_info: ["sessions", id, "proxy" | path_info]} = conn, _opts) do | ||
session = fetch_session!(id) | ||
pid = fetch_proxy_handler!(session) | ||
conn = prepare_conn(conn, path_info, ["sessions", id, "proxy"]) | ||
{conn, _} = Kino.Proxy.serve(pid, conn) | ||
|
||
halt(conn) | ||
end | ||
|
||
def call(%{path_info: ["apps", slug, id, "proxy" | 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 matching #{inspect(id)}" | ||
end | ||
|
||
session = fetch_session!(id) | ||
pid = fetch_proxy_handler!(session) | ||
conn = prepare_conn(conn, path_info, ["apps", slug, "proxy"]) | ||
{conn, _} = Kino.Proxy.serve(pid, conn) | ||
|
||
halt(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 matching #{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 matching #{id}" | ||
end | ||
end | ||
|
||
defp fetch_proxy_handler!(session) do | ||
case Livebook.Session.fetch_proxy_handler(session.pid) do | ||
{:ok, pid} -> pid | ||
{:error, _} -> raise NotFoundError, "could not find a kino proxy running" | ||
end | ||
end | ||
|
||
defp prepare_conn(conn, path_info, script_name) do | ||
%{conn | path_info: path_info, script_name: conn.script_name ++ script_name} | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
defmodule LivebookWeb.ProxyPlugTest do | ||
use LivebookWeb.ConnCase, async: true | ||
|
||
require Phoenix.LiveViewTest | ||
import Livebook.AppHelpers | ||
|
||
alias Livebook.{Notebook, Runtime, Session, Sessions} | ||
|
||
describe "session" do | ||
test "returns error when session doesn't exist", %{conn: conn} do | ||
session_id = Livebook.Utils.random_long_id() | ||
|
||
assert_error_sent 404, fn -> | ||
get(conn, "/sessions/#{session_id}/proxy/foo/bar") | ||
end | ||
end | ||
|
||
test "returns error when runtime is disconnected", %{conn: conn} do | ||
{:ok, session} = Sessions.create_session() | ||
|
||
assert_error_sent 404, fn -> | ||
get(conn, "/sessions/#{session.id}/proxy/foo/bar") | ||
end | ||
end | ||
|
||
test "returns the proxied response defined in notebook", %{conn: conn} do | ||
cell = %{ | ||
Notebook.Cell.new(:code) | ||
| source: """ | ||
Kino.Proxy.listen(fn conn -> | ||
conn | ||
|> Plug.Conn.put_resp_header("content-type", "application/text;charset=utf-8") | ||
|> Plug.Conn.send_resp(200, "used " <> conn.method <> " method") | ||
end) | ||
""" | ||
} | ||
|
||
cell_id = cell.id | ||
|
||
section = %{Notebook.Section.new() | cells: [cell]} | ||
notebook = %{Notebook.new() | sections: [section]} | ||
|
||
{:ok, session} = Sessions.create_session(notebook: notebook) | ||
{:ok, runtime} = Runtime.Embedded.new() |> Runtime.connect() | ||
|
||
Session.set_runtime(session.pid, runtime) | ||
Session.subscribe(session.id) | ||
|
||
Session.queue_cell_evaluation(session.pid, cell_id) | ||
assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _, _}} | ||
|
||
url = "/sessions/#{session.id}/proxy/" | ||
|
||
assert text_response(get(conn, url), 200) == "used GET method" | ||
assert text_response(post(conn, url), 200) == "used POST method" | ||
assert text_response(put(conn, url), 200) == "used PUT method" | ||
assert text_response(patch(conn, url), 200) == "used PATCH method" | ||
assert text_response(delete(conn, url), 200) == "used DELETE method" | ||
|
||
Session.close(session.pid) | ||
end | ||
end | ||
|
||
describe "app" do | ||
test "returns error when app doesn't exist", %{conn: conn} do | ||
slug = Livebook.Utils.random_short_id() | ||
session_id = Livebook.Utils.random_long_id() | ||
|
||
assert_error_sent 404, fn -> | ||
get(conn, "/apps/#{slug}/#{session_id}/proxy/foo/bar") | ||
end | ||
end | ||
|
||
test "returns the proxied response defined in notebook", %{conn: conn} do | ||
slug = Livebook.Utils.random_short_id() | ||
|
||
cell = %{ | ||
Notebook.Cell.new(:code) | ||
| source: """ | ||
Kino.Proxy.listen(fn conn -> | ||
conn | ||
|> Plug.Conn.put_resp_header("content-type", "application/text;charset=utf-8") | ||
|> Plug.Conn.send_resp(200, "used " <> conn.method <> " method") | ||
end) | ||
""" | ||
} | ||
|
||
app_settings = %{Notebook.AppSettings.new() | slug: slug, access_type: :public} | ||
section = %{Notebook.Section.new() | cells: [cell]} | ||
notebook = %{Notebook.new() | app_settings: app_settings, sections: [section]} | ||
|
||
Livebook.Apps.subscribe() | ||
pid = deploy_notebook_sync(notebook) | ||
|
||
assert_receive {:app_created, %{pid: ^pid, slug: ^slug}} | ||
|
||
assert_receive {:app_updated, | ||
%{ | ||
pid: ^pid, | ||
slug: ^slug, | ||
sessions: [ | ||
%{id: id, app_status: %{lifecycle: :active, execution: :executed}} | ||
] | ||
}} | ||
|
||
url = "/apps/#{slug}/#{id}/proxy/" | ||
|
||
assert text_response(get(conn, url), 200) == "used GET method" | ||
assert text_response(post(conn, url), 200) == "used POST method" | ||
assert text_response(put(conn, url), 200) == "used PUT method" | ||
assert text_response(patch(conn, url), 200) == "used PATCH method" | ||
assert text_response(delete(conn, url), 200) == "used DELETE method" | ||
|
||
Livebook.App.close(pid) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters