Phoenix send_download leading to a hangup in the liveview process with ranch listener error

Phoenix send_download leading to a hangup in the liveview process with ranch listener error

I have a phoenix liveview app, with a button to download images that does a live_redirect to downloads controller.
Downloads controller does the following:

  1. Download images from S3
  2. Zip the folder with images
  3. Send the zipped file to the client
  4. Redirect back to the live view page

All the above work except I run into the below error with send_download(step 3), due to which I see liveview loading forever.
Note: The zip file is downloaded on the client and able to access it.

Error Details:

[error] Ranch listener Kraftig.Web.Endpoint.HTTP had connection process started with :cowboy_clear:start_link/4 at #PID<0.8142.0> exit with reason: {:function_clause, [{:cowboy_http, :commands, [{:state, #PID<0.782.0>, Kraftig.Web.Endpoint.HTTP, #Port<0.12194>, :ranch_tcp, :undefined, %{env: %{dispatch: [{:_, [], [{:_, [], Phoenix.Endpoint.Cowboy2Handler, {Kraftig.Web.Endpoint, []}}]}]}, stream_handlers: [:cowboy_telemetry_h, :cowboy_stream_h]}, "", %{}, {{127, 0, 0, 1}, 44574}, {{127, 0, 0, 1}, 4003}, :undefined, #Reference<0.1228928527.871104513.128586>, true, 2, {:ps_request_line, 0}, 65535, 1, :done, 1000, [{:stream, 1, {:cowboy_telemetry_h, {:state, {:cowboy_stream_h, {:state, :undefined, Kraftig.Web.Endpoint.HTTP, #PID<0.8143.0>, :undefined, :undefined, :undefined, :undefined, 0, :nofin, "", 0, ...}}, #Function<0.122385210/1 in :cowboy_telemetry_h."-fun.metrics_callback/1-">, :undefined, %{body_length: 0, cert: :undefined, has_body: false, headers: %{"accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "accept-encoding" => "gzip, deflate, br", "accept-language" => "en-US,en;q=0.9", "connection" => "keep-alive", "cookie" => "ajs_user_id=%224726%22; ajs_anonymous_id=%22423a010e-8cfd-4aee-841a-e32359799c1e%22; _ga=GA1.1.1099789340.1616566097; _api_server_web_key=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiJmNWE3ZGVkMTg2OGEwMDRhYmZhZGY0NTZjYWQ5OGIzMCIsInVpZCI6bnVsbCwiYXVkIjoiSm9rZW4iLCJleHAiOjE2MTkxNTgzMTgsImlhdCI6MTYxNjU2NjMxOCwiaXNzIjoiSm9rZW4iLCJqdGkiOiIycG5pa3UxNjZ0cTVnOXRjaDAwMDBicDEiLCJuYmYiOjE2MTY1NjYzMTh9.MCdXHBj0Fg9iJL76Xdw0SU1nqwGio8RZ1BkylGavwjU; token=eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJlbXBvcm9zX3dlYiIsImNpZCI6NjQ3OTk2LCJleHAiOjE2MTkxMjQwOTUsImlhdCI6MTYxNjcwNDg5NSwiaXNzIjoiZW1wb3Jvc193ZWIiLCJqdGkiOiIzZTllMjhkNS1iNDQzLTRjOWYtYTRiMC1kNjI3YzgxZmU3ZTYiLCJuYmYiOjE2MTY3MDQ4OTQsInNzaWQiOjIsInN1YiI6Imd1ZXN0LTY0Nzk5Ni01MTcyNDQtMiIsInR5cCI6ImFjY2VzcyIsIndpZCI6NTE3MjQ0fQ.5dHm-3aKDuvoSEhoEFMyZkC2osYOaMVqFIMvE3waaIilcIzb4wHza7QlGtwMr2r90KWQ09oaicpwv3WzdsRg0Q; aptdecolocalfrontend=QlGtwMr2r90KWQ09oaicpwv3WzdsRg0Q; _kraftig_key=SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYdG5PWGNrM3ZWc013VVFYc2UtTFZVQTdx.WaDJSNTP-TvpdgPqkYQEi_oo7rz7LRL1AZ6tlTuSZjc; _tweeter_key=SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYSzFFQ3Q5UDdQWUoydVdkd25LbHR6bFpv.spZMg2JznqxGxKwvksUDFcON5DuA_wly2iPgtj1sUK4", "host" => "localhost:4003", "referer" => "http://localhost:4003/listing/19131", ...}, host: "localhost", method: "GET", path: "/listing/19131/download_images", peer: {{127, 0, ...}, 44574}, pid: #PID<0.8142.0>, port: 4003, qs: "", ...}, "302 Found", %{"cache-control" => "max-age=0, private, must-revalidate", "content-length" => "137", "content-type" => "text/html; charset=utf-8", "cross-origin-window-policy" => "deny", "date" => "Mon, 29 Mar 2021 16:52:01 GMT", "location" => "/listing/19131", "server" => "Cowboy", "set-cookie" => ["_kraftig_key=SFMyNTY.g3QAAAACbQAAAAtfY3NyZl90b2tlbm0AAAAYdG5PWGNrM3ZWc013VVFYc2UtTFZVQTdxbQAAAA1waG9lbml4X2ZsYXNodAAAAAFtAAAABGluZm9tAAAAE0Rvd25sb2FkIFN1Y2Nlc3NmdWw.XLcwz6Fy06g_w6wa8N_SyNiS72RqcNQk_pIP31PS3dw; path=/; HttpOnly"], "x-content-type-options" => "nosniff", ...}, Kraftig.Web.Endpoint.HTTP, -576427701839574753, :undefined, :undefined, :undefined, -576427698421276373, -576427698421276373, %{#PID<0.8143.0> => %{...}}, [], ...}}, "GET", :"HTTP/1.1", :undefined, :undefined, 0, []}], [{:child, #PID<0.8143.0>, 1, 5000, :undefined}]}, 1, [{:response, "302 Found", %{"cache-control" => "max-age=0, private, must-revalidate", "content-length" => "137", "content-type" => "text/html; charset=utf-8", "cross-origin-window-policy" => "deny", "date" => "Mon, 29 Mar 2021 16:52:01 GMT", "location" => "/listing/19131", "server" => "Cowboy", "set-cookie" => ["_kraftig_key=SFMyNTY.g3QAAAACbQAAAAtfY3NyZl90b2tlbm0AAAAYdG5PWGNrM3ZWc013VVFYc2UtTFZVQTdxbQAAAA1waG9lbml4X2ZsYXNodAAAAAFtAAAABGluZm9tAAAAE0Rvd25sb2FkIFN1Y2Nlc3NmdWw.XLcwz6Fy06g_w6wa8N_SyNiS72RqcNQk_pIP31PS3dw; path=/; HttpOnly"], "x-content-type-options" => "nosniff", "x-download-options" => "noopen", "x-frame-options" => "SAMEORIGIN", "x-permitted-cross-domain-policies" => "none", "x-request-id" => "FnDGSGZN4sso5A4AAHCC", "x-xss-protection" => "1; mode=block"}, ["<html><body>You are being <a href=\"/listing/19131\">redirected</a>.", "<iframe hidden src=\"/phoenix/live_reload/frame\"></iframe>", "</body>", "</html>"]}]], [file: '/workspace/deps/cowboy/src/cowboy_http.erl', line: 954]}, {:cowboy_http, :loop, 1, [file: '/workspace/deps/cowboy/src/cowboy_http.erl', line: 254]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]}]}

Code:
Phoenix Liveview:

        <%= live_redirect "Download Image(s)",
                      to: Routes.listing_path(@socket, :download_images, @product.id),
                      %>

Phoneix Downloads Controller:

defmodule DownloadsController do
  use App.Web, :controller

  @temp_downloads_dir "temp_downloads"
  @product_images_dir "#{@temp_downloads_dir}/product-images"

  def download_images(conn, %{"id" => product_id} = _params) do
    File.mkdir_p!(@product_images_dir)

    product_id
    |> list_images_by_id()
    |> download_images_from_s3()

    files = @product_images_dir |> File.ls!() |> Enum.map(&String.to_charlist/1)

    conn
    |> handle_client_download(files, &handle_cleanup/0)
    |> redirect(to: Routes.listing_index_path(conn, :index, product_id))
  end

  def handle_cleanup, do: File.rm_rf!(@temp_downloads_dir)

  def handle_client_download(conn, files, cleanup) when is_list(files) and length(files) > 0 do
    zip_file_path = "#{@temp_downloads_dir}/product-images.gz"
    :zip.create(zip_file_path, files, cwd: @product_images_dir)
    send_download(conn, {:file, zip_file_path}, content_type: "application/gzip")
    cleanup.()

    put_flash(conn, :info, "Download Successful")
  end

  def handle_client_download(conn, _files, cleanup) do
    cleanup.()
    put_flash(conn, :error, "Download failed. Try later or contact dev team if issue persists.")
  end

  def download_images_from_s3(images) when is_list(images) and length(images) > 0 do
    images
    |> Task.async_stream(
      fn image ->
        image
        |> get_s3_download_path()
        |> download_from_s3(image.id)
      end,
      max_concurrency: 5,
      timeout: 10_000
    )
    |> Enum.to_list()
  end

  def download_images_from_s3(_, _) do
    {:error, :s3_downloads_failure}
  end

  defp get_s3_download_path(%{s3_path: s3_path}), do: "product-images/#{s3_path}/test.jpg"
  defp get_s3_download_path(%{path: path}), do: path

  def download_from_s3(image_path, id) do
    bucket = Application.get_env(:ex_aws, :s3)[:bucket]

    bucket
    |> ExAws.S3.download_file(image_path, "#{@product_images_dir}/img#{id}.jpg")
    |> ExAws.request!()
  end
end

Appreciate if anyone can point me in the right direction to resolve the issue.

Welcome @gaurav316 !

It’s likely the live_redirect to a controller (not LiveView) route. Try using a regular link and see if that clears up the error.

Thanks for the quick response @mcrumm.

Updated to link as per your suggestion and also tried the following but still running into the same issue.

        <%= link "Download Image(s)",
                      to: Routes.listing_path(@socket, :download_images, @product.id),
                      %>

  def download_images(conn, %{"id" => product_id} = _params) do
    File.mkdir_p!(@product_images_dir)

    product_id
    |> list_images_by_id()
    |> download_images_from_s3()

    files = @product_images_dir |> File.ls!() |> Enum.map(&String.to_charlist/1)

    conn
    |> handle_client_download(files, &handle_cleanup/0)
**̶ ̶ ̶ ̶|̶>̶ ̶r̶e̶d̶i̶r̶e̶c̶t̶(̶t̶o̶:̶ ̶R̶o̶u̶t̶e̶s̶.̶l̶i̶s̶t̶i̶n̶g̶_̶i̶n̶d̶e̶x̶_̶p̶a̶t̶h̶(̶c̶o̶n̶n̶,̶ ̶:̶i̶n̶d̶e̶x̶,̶ ̶p̶r̶o̶d̶u̶c̶t̶_̶i̶d̶)̶)̶
**   |> put_status(:ok)
**   |> send_resp(200,"")
  end
1 Like

Hi :wave:

Did you manage to resolve this issue?

Kind Regards,
C