View Source Kino.Proxy (Kino v0.14.0)

Functionality for handling proxy requests forwarded from Livebook.

Livebook proxies requests at the following paths:

  • /proxy/sessions/:id/*path - a notebook session

  • /proxy/apps/:slug/sessions/:session_id/*path - a specific app session

  • /proxy/apps/:slug/*path - generic app path, only supported for single-session apps. If the app has automatic shutdowns enabled and it is not currently running, it will be automatically started

You can define a custom listener to handle requests at these paths. The listener receives a Plug.Conn and it should use the Plug API to send the response, for example:

Kino.Proxy.listen(fn conn ->
  Plug.Conn.send_resp(conn, 200, "hello")
end

Plug dependency

In order to use this feature, you need to add :plug as a dependency.

Examples

Using the proxy feature, we can use Livebook apps to build APIs. For example, we could provide a data export endpoint:

Kino.Proxy.listen(fn
  %{path_info: ["export", "data"]} = conn ->
    data = "some data"

    conn
    |> Plug.Conn.put_resp_header("content-type", "application/csv")
    |> Plug.Conn.send_resp(200, data)

  conn ->
    conn
    |> Plug.Conn.put_resp_header("content-type", "application/text")
    |> Plug.Conn.send_resp(200, "use /export/data to get extract the report data")
end)

Once deployed as an app, the API client would be able to export the data by sending a request to /apps/:slug/proxy/export/data.

Authentication

The paths exposed by Kino.Proxy don't use the authentication mechanisms defined in your Livebook instance.

If you need to authenticate requests, you should implement your own authentication mechanism. Here's a simple example.

Kino.Proxy.listen(fn conn ->
  expected_token = "my-secret-api-token"

  with ["Bearer " <> user_token] <- Plug.Conn.get_req_header(conn, "authorization"),
       true <- Plug.Crypto.secure_compare(user_token, expected_token) do
    Plug.Conn.send_resp(conn, 200, "hello")
  else
    _ ->
      conn
      |> Plug.Conn.put_resp_header("www-authenticate", "Bearer")
      |> Plug.Conn.send_resp(401, "Unauthorized")
  end
end)

Summary

Functions

Registers a request listener.

Types

@type plug() :: (Plug.Conn.t() -> Plug.Conn.t()) | module() | {module(), term()}

Functions

@spec listen(plug()) :: DynamicSupervisor.on_start_child()

Registers a request listener.

Expects the listener to be a plug, that is, one of:

  • a function plug: a fun(conn) function that takes a Plug.Conn and returns a Plug.Conn.

  • a module plug: a module atom or a {module, options} tuple.