pythonfile-uploadfastapistarlette

FastAPI: How to upload a file without using multipart/form-data request?


I have a FastAPI endpoint for handling file uploads that looks something like this:

@app.post('/upload')
async def accept_some_file(f: UploadFile):
    content = await f.read()
    # ... do stuff with content and generate a response

but this appears to only work with multipart/form-data encoded payloads.

I'd like to be able to send file bytes directly through a request that looks like this:

POST /upload HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.79.1
Accept: */*
Content-Type: image/jpeg
Content-Length: 11044

... image bytes

Is there a FastAPI setting I can use to allow this? Or is there another request type that makes more sense for this use case?


Solution

  • Option 1

    You could get the request body as bytes using await request.body(). If you would like to do that in the synchronous way (i.e., using def endpoint instead—see here for def vs async def), please have a look at this answer.

    from fastapi import Request
    
    @app.post('/upload')
    async def upload_file(request: Request):
        body = await request.body()
    

    Option 2

    You could also access the request body as a stream, as described in this answer (under "Update" section) and this answer (which provides another working example, demonstrating how to receive both Files and Form data using request.stream())—please refer to those answers for more details, as well as both client and server examples. In this way, the byte chunks are provided without first storing the entire body to memory (see Starlette's documentation as well)—in contrast to using request.body(), as shown in the previous option, which could lead to issues, if the request body (e.g., a file) couldn't fit into the server's RAM.

    Note that the example below stores the byte chunks in a variable named body (essentially, storing them into RAM), but that's for demo purposes. If, however, you need to write the byte chunks to the disk as they arrive, please have a look at the linked answers above on how to do so using aiofiles or streaming_form_data libraries.

    @app.post('/upload')
    async def upload_file(request: Request):
        chunks = list()
        async for chunk in request.stream():
            chunks.append(chunk)
        body = b''.join(chunks)