Packages

Rich, real-time user experiences with server-rendered HTML

Security advisory: This version has known vulnerabilities. View advisories

Current section

Files

Jump to
phoenix_live_view lib phoenix_live_view upload_writer.ex
Raw

lib/phoenix_live_view/upload_writer.ex

defmodule Phoenix.LiveView.UploadWriter do
@moduledoc ~S"""
Provides a behavior for writing uploaded chunks to a final destination.
By default, uploads are written to a temporary file on the server and
consumed by the LiveView by reading the temporary file or copying it to
a durable location. Some use cases require custom handling of the uploaded
chunks, such as streaming a user's upload to another server. In these cases,
we don't want the chunks to be written to disk since we only need to forward
them on.
**Note**: Upload writers run inside the channel uploader process, so
any blocking work will block the channel, and errors will crash the channel process.
Custom implementations of `Phoenix.LiveView.UploadWriter` can be passed to
`allow_upload/3`. To initialize the writer with options, define a 3-arity function
that returns a tuple of `{writer, writer_opts}`. For example imagine
an upload writer that logs the chunk sizes and tracks the total bytes sent by the
client:
socket
|> allow_upload(:avatar,
accept: :any,
writer: fn _name, _entry, _socket -> {EchoWriter, level: :debug} end
)
And such an `EchoWriter` could look like this:
defmodule EchoWriter do
@behaviour Phoenix.LiveView.UploadWriter
require Logger
@impl true
def init(opts) do
{:ok, %{total: 0, level: Keyword.fetch!(opts, :level)}}
end
@impl true
def meta(state), do: %{level: state.level}
@impl true
def write_chunk(data, state) do
size = byte_size(data)
Logger.log(state.level, "received chunk of #{size} bytes")
{:ok, %{state | total: state.total + size}}
end
@impl true
def close(state, reason) do
Logger.log(state.level, "closing upload after #{state.total} bytes, #{inspect(reason)}")
{:ok, state}
end
end
When the LiveView consumes the uploaded entry, it will receive the `%{level: ...}`
returned from the meta callback. This allows the writer to keep state as it handles
chunks to be later relayed to the LiveView when consumed.
## Close reasons
The `close/2` callback is called when the upload is complete or cancelled. The following
values can be passed:
* `:done` - The client sent all expected chunks and the upload is awaiting consumption
* `:cancel` - The upload was canceled, either by the server or the client navigating away.
* `{:error, reason}` - The upload was canceled due to an error returned from `write_chunk/2`.
For example, if `write_chunk/2` returns `{:error, :enoent, state}`, the upload will be cancelled
and `close/2` will be called with the reason `{:error, :enoent}`.
"""
@callback init(opts :: term) :: {:ok, state :: term} | {:error, term}
@callback meta(state :: term) :: map
@callback write_chunk(data :: binary, state :: term) ::
{:ok, state :: term} | {:error, reason :: term, state :: term}
@callback close(state :: term, reason :: :done | :cancel | {:error, term}) ::
{:ok, state :: term} | {:error, term}
end