-
-
Notifications
You must be signed in to change notification settings - Fork 6.2k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Strategies for limiting upload file size #362
Comments
Ok, I've found an acceptable solution. But it relies on Edit: I've added a check to reject requests without from starlette import status
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import Response
from starlette.types import ASGIApp
class LimitUploadSize(BaseHTTPMiddleware):
def __init__(self, app: ASGIApp, max_upload_size: int) -> None:
super().__init__(app)
self.max_upload_size = max_upload_size
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
if request.method == 'POST':
if 'content-length' not in request.headers:
return Response(status_code=status.HTTP_411_LENGTH_REQUIRED)
content_length = int(request.headers['content-length'])
if content_length > self.max_upload_size:
return Response(status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE)
return await call_next(request) using it is quite straightforward: app = FastAPI()
app.add_middleware(LimitUploadSize, max_upload_size=50_000_000) # ~50MB The server sends HTTP 413 response when the upload size is too large, but I'm not sure how to handle if there's no |
You can reply HTTP 411 if |
@tiangolo This would be a great addition to the base package |
You can use an ASGI middleware to limit the body size. Example: https://github.com/steinnes/content-size-limit-asgi |
Thanks everyone for the discussion here! So, here's the thing, a file is not completely sent to the server and received by your FastAPI app before the code in the path operation starts to execute. So, you don't really have an actual way of knowing the actual size of the file before reading it. You could require the from fastapi import FastAPI, File, Header, Depends, UploadFile
async def valid_content_length(content_length: int = Header(..., lt=50_000_000)):
return content_length
app = FastAPI()
@app.post('/upload', dependencies=[Depends(valid_content_length)])
async def upload_file(file: UploadFile = File(...)):
# do something with file
return {"ok": True} And then you could re-use that
Another option would be to, on top of the header, read the data in chunks. And once it's bigger than a certain size, throw an error. E.g. from typing import IO
from tempfile import NamedTemporaryFile
import shutil
from fastapi import FastAPI, File, Header, Depends, UploadFile, HTTPException
from starlette import status
async def valid_content_length(content_length: int = Header(..., lt=80_000)):
return content_length
app = FastAPI()
@app.post("/upload")
def upload_file(
file: UploadFile = File(...), file_size: int = Depends(valid_content_length)
):
real_file_size = 0
temp: IO = NamedTemporaryFile(delete=False)
for chunk in file.file:
real_file_size += len(chunk)
if real_file_size > file_size:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, detail="Too large"
)
temp.write(chunk)
temp.close()
shutil.move(temp.name, "/tmp/some_final_destiny_file")
return {"ok": True} |
Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues. |
Hey @jd-0001, this is how I did it ... import aiofiles
from fastapi import FastAPI, File, Header, Depends, UploadFile, HTTPException
from starlette import status
async def valid_content_length(content_length: int = Header(..., lt=80_000)):
return content_length
app = FastAPI()
@app.post("/upload")
async def upload_file(
file: UploadFile = File(...), file_size: int = Depends(valid_content_length)
):
output_file = f"/path/to/{file.filename}"
real_file_size = 0
try:
async with aiofiles.open(f"{output_file}", "wb") as out_file:
while content := await file.read(1024): # async read chunk
real_file_size += len(content)
if real_file_size > file_size:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="Too large",
)
await out_file.write(content) # async write chunk
msg = f"Successfuly uploaded {file.filename} for processing"
except IOError:
msg = "There was an error uploading your file"
return {"message": msg} |
Thanks @engineervix I will try it for sure and will let you know. |
for the check file size in bytes, you can use
and then set condition for check size |
#362 (comment) |
Thanks for everyone's valuable tips here. This is how I did it, trying to avoid file_destination = Path(destination)
try:
# Writes file to destination while validating file size
real_file_size = 0
with file_destination.open("wb") as buffer:
for chunk in upload_file.file:
real_file_size += len(chunk)
if real_file_size > max_fize_size:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, detail="Too large"
)
buffer.write(chunk)
except HTTPException:
os.unlink(file_destination)
raise
finally:
upload_file.file.close() |
This is still an issue with the latest version of FastAPI. from fastapi import FastAPI, status, UploadFile
from starlette.responses import Response
from starlette.types import ASGIApp
from starlette.requests import Request
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from typing import List
from enum import Enum
class AudioFileTypeName(str, Enum):
mp3 = "audio/mp3"
mpeg = "audio/mpeg"
ogg = "audio/ogg"
wave = "audio/wave"
wav = "audio/wav"
@classmethod
def list(cls):
return list(map(lambda c: c.value, cls))
class ValidateUploadFileMiddleware(BaseHTTPMiddleware):
def __init__(
self,
app: ASGIApp, max_size: int = 1048576, # 1MB
file_types: List[str] = AudioFileTypeName.list()
) -> None:
super().__init__(app)
self.max_size = max_size
self.file_types = file_types
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
if request.method == 'POST':
form = await request.form()
content_type = form[next(iter(form))].content_type
if content_type not in self.file_types:
return Response(status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
if 'content-length' not in request.headers:
return Response(status_code=status.HTTP_411_LENGTH_REQUIRED)
content_length = int(request.headers['content-length'])
if content_length > self.max_size:
return Response(status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE)
return await call_next(request)
app = FastAPI()
# File size upload middleware does not work because of a bug in starlette. This code is broken https://github.com/tiangolo/fastapi/issues/362
app.add_middleware(
ValidateUploadFileMiddleware
)
@app.post("/upload")
def upload(file: UploadFile):
return {'success': True}
@app.get("/health")
def health():
return Response(status_code=status.HTTP_200_OK) I believe this should be fixed since it is a very common requirement for APIs. |
@em1208 As noted above, you need to make sure to check the size of the bytes you are receiving as you receive them. This is possible to do following the approach in either of these comments:
If you want this to be handled more automatically for you, I think it might be possible by subclassing |
@dmontagu I think the middleware should be the best approach for most cases but using dependencies should also be supported in case of different limits for different views. Do you agree that the middleware approach should be supported and needs to be fixed? |
I wouldn't use the word "fixed" (in "it needs to be fixed") because I think this is a feature request rather than a bug. But I do think it's a reasonable feature request. That said, a quick google for "asgi middleware file size" found me this: https://github.com/steinnes/content-size-limit-asgi. I haven't tried it at all but it looks like it is an ASGI middleware (so I think should work with FastAPI/starlette) and appears to implement content length validation. Edit: They specifically include an example using starlette in the README.md, so I'm pretty sure this should work for your use case. |
@dmontagu thanks for pointing out this solution https://github.com/steinnes/content-size-limit-asgi, I verified that it works. Still I believe there is an issue in the documentation because the approach #362 (comment) should work but unfortunately it does not. I would be happy to work on this and open a PR once we agree on the approach to solve this problem / feature request. |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Description
I'm trying to create an upload endpoint. I want to limit the maximum size that can be uploaded.
My endpoint looks like this:
I checked out the source for
fastapi.params.File
, but it doesn't seem to add anything overfastapi.params.Form
.The only solution that came to my mind is to start saving the uploaded file in chunks, and when the read size exceeds the limit, raise an exception. But I'm wondering if there are any idiomatic ways of handling such scenarios?
The text was updated successfully, but these errors were encountered: