Phoenix File Download

What the correct way to Phoenix send a new generated file (non text, example gz).

I’m trying with full path (with application dir) or regular path, neither are working.
Tried with File.read! also.

conn
|> put_resp_content_type("application/gzip")
|> put_resp_header("content-disposition", "attachment; filename=#{filename}.gz")
|> send_file(200, full_path)

I can send text files with this:

conn
|> put_resp_content_type(filetype)
|> put_resp_header("content-disposition", "attachment; filename=#{filename}")
|> send_resp(200, content)

The only difference is compress or not:

attr =
  case compress do
    true ->
      :compressed
    false ->
      :utf8
  end
file =
  File.open!(temp_file, [:write, attr])

In both cases, file is corrected created at “temp/”, but I still can’t send compressed file.

send_file/3 expects a file path as the third argument. It seems that in your code, instead of a file path you’re passing in a variable called content which seems to suggest it’s a binary with the actual file contents you want to send.

It was a mistake when I created the question. I’m sending correct full path to both cases.
The only difference is compress or not:

attr =
  case compress do
    true ->
      :compressed
    false ->
      :utf8
  end
file =
  File.open!(temp_file, [:write, attr])

In both cases, file is corrected created at “temp/”, but I still can’t send compressed file.

So you pass file (which is :file.io_device()) to send_file (which expects a binary path to the file)? Can it be the problem?

I’m passing the same type to both. Binnary paths.

"..temp/report.csv"

"..temp/report.csv.gz"

From IEX i can read them both:

File.read!("..temp/report.csv.gz")

File.read!("..temp/report.csv")

When I inspect Phoenix, the gz returns empty.

How did you verify that the .gz file actually exists then? Maybe you could show the full code in context.

I can see and fully test both compressed and non compressed on my filesystem (and on IEX).

All other functions related:

  def to_csv(data, file_name, compress \\ true) do
    {extension, attr} =
      case compress do
        true ->
          {".gz", [:write, :utf8, :compressed]}
        "true" ->
          {".gz", [:write, :utf8, :compressed]}
        _ ->
          {"", [:write, :utf8]}
      end
    temp_file =
      file_name <> ".csv" <> extension
    file =
      File.open!(temp_file, attr)
    stream_data =
      data
      |> Stream.map(&(&1))
      |> CSVLixir.write
    {_full_path, _content} =
      stream_file(stream_data, file, temp_file)
  end

  def stream_file(stream_data, file, file_name) do
    _stream_content =
      stream_data
      |> Stream.each(&(IO.write(file, &1)))
      |> Stream.run()
    dir =
      Application.app_dir(:phishx) |> String.split("_") |> List.first()
    full_path =
      dir <> file_name
    content =
      File.read!(full_path)
    {full_path, content}
  end

  def content(filetype, filename, module, target, subdomain, params) do
    compress =
      params["compress"]
    {_final_status, _full_path, _final_content} =
      case filetype do
        "csv" ->
          {full_path, content} =
            module
            |> apply(target, [subdomain, params])
            |> Format.multiple_to_array()
            |> Format.to_csv(filename, compress)
          {:ok, full_path, content}
      end
  end

  def deliver(conn, status, full_path, _filetype, filename, content) do
    case status do
      :ok ->
        conn
        |> put_resp_content_type("application/gzip")
        |> put_resp_header("content-disposition", "attachment; filename=#{filename}.gz")
        |> send_file(200, full_path)
      :error ->
        conn
        |> put_flash(:error, gettext("Export failed."))
        |> redirect(to: NavigationHistory.last_path(conn, 1))
    end
  end

Is the :utf8 option really compatible with :compressed in [:write, :utf8, :compressed]?

So how is the deliver function called?

It’s working with Path.expand().
Now I can call compress or not and both return data.

  def base_dir(base \\ "temp/") do
    base
    |> Path.expand()
    |> Kernel.<>("/")
  end

This problem resulted in a module that I will upload to HEX to work with really deeped maps.