How to Return a File as a Downloadable Attachment in Oxygen.jl Endpoint

I’m using Oxygen.jl to implement an endpoint that retrieves data, processes it, and creates a DataFrame. I’d like the /dataframe POST method to return a downloadable file named test.csv containing the DataFrame. I tried adding Content-Disposition: attachment; filename="test.csv" to the headers of the HTTP response in the handler, but it didn’t work.

Here’s some sample code that mimics the situation:

using CSV
using DataFrames
using HTTP
using Oxygen

# Create a sample DataFrame
df = DataFrame(
    Name = ["Alice", "Bob", "Charlie"],
    Age = [25, 30, 35],
    City = ["New York", "Los Angeles", "Chicago"]
)

# Define the endpoint to return the DataFrame as a downloadable CSV file
@post "/dataframe" function (req::HTTP.Request)
    io = IOBuffer()
    CSV.write(io, df)
    seekstart(io)
    return HTTP.Response(
        200,
        [
            "Content-Type" => "text/csv",
            "Content-Disposition" => "attachment; filename=\"test.csv\"",
        ],
        io,
    )
end

# Start the Oxygen server
serve()

Does anyone have suggestions for how to force the download as a file or know why this approach isn’t working?

I copied/pasted your code and changed @post to @get and it works just fine when visiting localhost:8080/dataframe in my browser. If I define it to accept POST requests (i.e., leaving your code as is), I can hit the same URL in Postman and it returns the .csv data. Are you getting an error, or what’s the problem exactly? If you are trying to just put localhost:8080/dataframe in the address bar of your browser, you will need to redefine it with @get.

Thanks for your reply! I forgot to change the method to GET in the example. Originally, I was passing some parameters in the request body to create a DataFrame dynamically, so I used the POST method.

However, when I test the endpoint on the Swagger page created by Oxygen, or if I use Postman or a curl command in the terminal, I only get the contents of the DataFrame displayed in the response section (Swagger/Postman) or printed in the terminal. What I’m looking for is for the test.csv file to be directly downloaded, without having to save it manually.

Right now, if I press the Save Response button in Postman, it opens a window to select the path to save it as test.csv. Is there a way to make the file download automatically instead of just showing the content in the response?

I think you would have to write some additional code to write the contents of the file to your local disk. So after you make the request, I think you could just do something like:

response = HTTP.get(url)

if response.status == 200
    open(filename, "w") do file
        write(file, response.body)
    end
else
    # handle other cases
end

In the browser, user interaction will always be necessary (a browser is never going to allow a site to save files to the user’s machine without their knowledge/consent).

Correct. I was hoping that if I used the Swagger page, the browser would prompt me to download the file and then proceed with the download automatically.

Hello @ehsani63 ,
but this behavior is really just a special case for Swagger / Postman, which treat the Content-Disposition header differently than a Browser would do.
If you call your /dataframe endpoint directly in the Browser the CSV is downloaded as it is supposed to be.

In case you just want something to click on in the Browser to download your file, you could add a minimal UI like:

@get "/" function (req::HTTP.Request)
    Oxygen.html("<a href=/dataframe>Download CSV</a>")
end

If you need more, I can recommend the combination of Mustache.jl and HTMX.

BR Stefan

2 Likes