FastAPI's RedirectResponse doesn't work as expected when Swagger UI is used

Viewed 96

I have a FastAPI app with a download endpoint. What this download endpoint does is to use a BlobServiceClient (for Azure Blob Storage) to generate a token and a Blob URL to a file specified in the request. What I want to do is to redirect the user to that URL. Here is a code snippet of the download enpoint (I commented some things out because I'm not allowed to show the code).

@router.get("..path", tags=["some tags"], summary=..., responses={404: {"model": ...}, 403: {"model": ...}, 307: {"model": ...}}, response_model_exclude_none=True)
async def download_file(
        # there's a depends on an API key
        blob_path: str = Query(
            ...
        )):
credential = ClientSecretCredential(...)  //secrets
blob_service_client = BlobServiceClient(f"https://{storage_account}.blob.core.windows.net", credential=credential)
user_delegation_key = blob_service_client.get_user_delegation_key(key_start_time=datetime.utcnow(),key_expiry_time=datetime.utcnow() + timedelta(minutes=30))
    
token = generate_blob_sas(account_name=...,                                 
                          container_name=...,                                 
                          blob_name=blob_path,
                          user_delegation_key=user_delegation_key,
                          permission=BlobSasPermissions(read=True),
                          expiry=datetime.utcnow() + timedelta(minutes=30))
    
blob_url = f'https://{storage_account}.blob.core.windows.net/{container_name}/{blob_path}?{token}' 
print(blob_url)  
response = RedirectResponse(blob_url) 
return response

What I expected is the query to be executed, and after the response is returned, the download to start in the background or in a separate tab. What I've got instead is a different response as you can see in the Swagger:

enter image description here enter image description here

I also had a look in the Network tab to see what is happening with that request:

enter image description here enter image description here

Looks like there is an OPTIONS request and I assume that I'm getting the response to that request. Not sure if this is how Swagger handles the request. Any idea how/why this is happening and how to fix it? Thank you!

1 Answers

To start with, the HTTP OPTIONS, in CORS, is a preflight request that is automatically issued by the browser, before the actual request—is not the one that returns the File response. It requests the permitted communication options for a given server, and the server responds with an Access-Control-Allow-Methods header including a set of permitted methods (e.g., Access-Control-Allow-Methods: OPTIONS, GET, HEAD, POST, DELETE). The preflight response can be optionally cached for the requests created in the same URL using Access-Control-Max-Age header, thus allowing the server to limit the number of preflight requests. The value of this header is expressed in seconds; hence, allowing caching for 10 minutes, for example, would look as Access-Control-Max-Age: 600.

As for the RedirectResponse, Swagger UI always follows redirect responses. In a fetch request, for instance, the redirect parameter would be set to follow, indicating that the redirect should be followed. This means that Swagger UI follows the redirect and waits for the response to be completely received before providing you with a Download file link (as shown in the screenshot you provided above) to download the file. That is also why you can't see the download starting either in the background or a new tab. As mentioned in the linked github post above, it is not possible to change, for example, the redirect to manual, so that Swagger doesn't follow the redirect, which could allow you to handle it differently.

Instead of using Swagger UI to test that specific endpoint, you can either test it directly through typing the URL to your API endpoint in the address bar of your browser (since it is a GET endpoint, you can do that), or create your own custom Template (or use HTMLResponse) and submit an HTML <form>, as shown in this answer.

Related