4

Currently I'm struggling a bit with FastAPI and file serving.

In my project I have the following workflow. Client sends a payload containing the necessary payload to download a file from a third party provider.

I need to send a payload to the backend, it's necessary and since a resource is created (file downloaded), I assumed that POST would be the Method for this endpoint, but let me show you an example.

from fastapi import FastAPI, Form
from fastapi.responses import FileResponse

import os

app = FastAPI()



@app.post("/download_file")
async def download():
    
    url = 'https://file-examples-com.github.io/uploads/2017/10/file-sample_150kB.pdf'
    os.system('wget %s'%url)
    

    return FileResponse("file-sample_150kB.pdf")


@app.get("/get_file")
async def get_file():
    return FileResponse("/home/josec/stackoverflow_q/file-sample_150kB.pdf")

If I go to http://localhost:8000/get_file, I get the file displayed on the web page! However that's not what I'm looking for! I want the file to be downloaded on the client side, either be via a browser or via cli!

The following script does not download any file, except when you paste it in the browser where you can look at it.

import requests

url = "http://localhost:8000/get_file"


response = requests.request("GET", url)

print(response.json())

This one is not working as well!

import requests

url = "http://localhost:8000/download_file"


response = requests.request("POST", url)

print(response.json())

My questions are:

  • Should I just use GET? If yes how would I pass parameters, on the url? some strings that I'm sending with the post request can be very long, don't know if that could be an issue.

  • How can I return a file to the user? Download it immediatly to the user in a return statement of the function endpoint!

  • Can I do it with POST?

If you guys need anything else from me please do tell :-)

Best,

Jose

José Rodrigues
  • 467
  • 3
  • 12

1 Answers1

4

I think the problem lies in the fact that you are not specifying the type of the file, so the browser simply opens it.

In the more advanced topics, there are instructions about how to define the return type of a file.

https://fastapi.tiangolo.com/advanced/additional-responses/#additional-media-types-for-the-main-response

Below the example taken from the docs

    @app.get(
    "/items/{item_id}",
    response_model=Item,
    responses={
        200: {
            "content": {"image/png": {}},
            "description": "Return the JSON item or an image.",
        }
    },
)
async def read_item(item_id: str, img: Optional[bool] = None):
    if img:
        return FileResponse("image.png", media_type="image/png")
    else:
        return {"id": "foo", "value": "there goes my hero"}

As you can see, the FileResponse is specifying the type of the media, so that the client knows how to handle it.

Regarding the GET/POST question, I believe POST is correct, because you are creating something, although I also believe it is wrong to ask back the data that you just created. I mean, you just sent the data and now you're asking the same data? In my opinion this is wrong, but you could also download via a GET request once the POST request is done.

EDIT

Sorry for not replying, I got totally lost.

Anyways, regarding your second questions, now I understand your intent is to make it graphically downloadable. This is quite simple:

  • download the file via javascript using ajax
  • take the result of the ajax and insert it into an HTML-element
  • trigger the click of the HTML-element and the user will be asked where to save the file (or download just starts, depending on the settings the user has activated on his system)

Below the resources to achieve what you're looking for

After download via ajax, insert the content into a newly created element as in How to download file with javascript?

The href parameter has to be encoded as in Using HTML5/JavaScript to generate and save a file. The exact data type depends on the data type of your file. A simple search on any search engine will provide the needed data type.

Then, you'll have to insert the element you just created somewhere in your HTML and activate the click functionality.

lsabi
  • 3,641
  • 1
  • 14
  • 26
  • I tried with an image this time, and the same thing is happening .... Just shows in browser – José Rodrigues Apr 16 '21 at 12:17
  • @JoséRodrigues try to specify the `filename` in the `FileResponse`, if you don't by default your browser will try to visualize the file if it is a supported file type. – Georgi Stoyanov May 27 '21 at 14:43