File download phoenix

I am creating a csv and saving it to the static folder like this:

def get_file_path() do
    file_path = "#{:code.priv_dir(:my_app)}/static/myfolder/#{Ecto.UUID.generate()}.csv"
  
    File.mkdir_p!(Path.dirname(path_to_priv_static <> "/myfolder/"))

    {:ok, file_path}
  end

def write() do
  {:ok, file_path} = get_file_path()
  file = File.open!(file_path, [:write, :utf8])
  
  ...write csv...
  
  File.close(file)
end

This works fine and Phoenix is setup to serve static files out of my_folder folder. The issue is that after a .csv is create if I paste https://.../my_folder/<generated_ecto_uuid>.csv into the address bar it downloads the file. If i paste it in the address bar again and hit enter it takes me to a page “Not Found” and nothing is downloaded. If i refresh the tab and then paste the address in the address bar again it downloads fine, paste in the address bar agin and hit enter back to “Not Found” page. rinse repeat.

So the CSVs are being saved in the priv>static>... directory. There is also an assets>static directory that does not seem to display the same behavior as above when I try to download a file from it using the url, it works everytime i paste and hit enter. Also weird, this problem only happens on prod. Locally I can paste the csv link in the address bar and download as many times as I want without having to refresh the tab in between. Anyone have any ideas whats up or a better way to save the csv to avoid issues this directory might be having?

How about AWS S3?

2 Likes

The correct folder to store run time generated, user downloadable files shall be priv/static/... not assets/static/... the latter is a source dir for assets. There must be something else wrong in your code; check your console for messages.

1 Like

Just to be clear, is this just a typo when you wrote out the post? Because there’s a difference in your path names my_folder and myfolder

1 Like

Yes, that is a typo. It works fine for the most part, just not every time specifically outside of localhost. On prod its like the resource is there but unless i refresh the tab i can only access it once otherwise i get a “Not Found”.

1 Like

How are you hosting the application? I’ve seen similar behavior on PaaS hosting like Heroku, where each dyno has a separate local filesystem; if the upload and the download don’t hit the same one the file won’t be found.

3 Likes

Another anecdote, which is close to how I’m actually triggering the download for the user. If I go into the the js console and do:

element = document.createElement('a')
element.href = '/reports/8e124ed0-ec3d-43e2-a0f7-687fe7c2358e.csv'
element.download= 'file-name-here'

almost like clockwork I can enter element.click() over and over and it will alternate between successful download and then “No File”. Again, this only happens in Prod, in Dev if I do this it is 100% successful as many times as you want to attempt it. Here is my Plug.Static (these CSVs are located in priv>static>reports):

plug(Plug.Static,
    at: "/",
    from: :my_app,
    gzip: false,
    only:
      ~w(reports css fonts images js manifest.json robots.txt)
  )

Ah you may be onto something this is Docker with 2 nodes. Any idea if there is a way to get around this?

If both nodes are serving files, you’ll need to make the place files are written to accessible to both nodes. The Docker docs describe some options.

An alternative approach would be to instead have both nodes use a common “object storage” server - for instance, @APB9785’s suggestion of S3.

1 Like

I ended up just pushing these files to S3 since we are already using that. Thanks for the help!