簡體   English   中英

使用 fastapi UploadFile 驗證文件類型和擴展名

[英]validate file type and extention with fastapi UploadFile

我目前正在從事一個小項目,該項目涉及創建一個允許用戶上傳jar文件的fastapi服務器。

基本上我有這條路線:

@app.post("/upload")
async def upload(jar_file: UploadFile = File(...)):

我真的很想檢查並驗證該文件是否真的是jar文件。

我可以自己實現它,但我很好奇fastapi或任何其他 package 是否提供此功能。

您可以檢查 MIME 類型 (https://fastapi.tiangolo.com/tutorial/request-files/#uploadfile )。

@app.post("/upload")
async def upload(jar_file: UploadFile = File(...)):
    if jar_file.content_type != "application/java-archive":
        raise HTTPException(400, detail="Invalid document type")
    return {"filename": "jar_file.filename"}

我有同樣的需求,並且由於我的文件相對較大,我希望能夠在將文件上傳到后端(至少不是整個文件)之前獲得錯誤消息,如rezan21所述。

這是使它起作用的方法。 請注意,由於 Stalette 的一些限制,有多種解決方法,例如1. 這個用於讀取請求主體異步生成器和2. 這個問題處理這個確切的需要。

首先,我直接從 SwaggerUI選擇文件輸入讀取文件,因此沒有傳遞額外的標頭來指示前端或 api 消費者可以讀取的文件擴展名或 MIME 類型。

然后,我想直接在路由定義中設置文件,就像任何其他依賴項一樣。 單獨的依賴項在這里不起作用,因為它僅在整個文件上傳后才被調用。

因此,我的 csv 和 excel 文件的當前工作解決方案是使用自定義BaseHTTPMiddleware ,異步讀取請求正文並從文件本身獲取“標頭”。

根據我的推斷,這獲取了 body 異步生成器的第一個塊,並且它具有正在上傳的文件的信息。 為了防止程序停頓,get_body function按照1.

import re

from fastapi import HTTPException, Request, status
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware

from dependencies import ContentTypeChecker


def get_content_type_from_body(body):
    content_type_match = re.search(rb'Content-Type: ([^\r\n]+)', body)

    if content_type_match:
        content_type = content_type_match.group(1).decode("utf-8")
    return content_type


async def set_body(request: Request, body: bytes):
    async def receive():
        return {"type": "http.request", "body": body}
    request._receive = receive


async def get_body(request: Request) -> bytes:
    body = await request.body()
    await set_body(request, body)
    return body


class ValidateContentTypeMiddleware(BaseHTTPMiddleware):
    def __init__(self, app):
        super().__init__(app)

    async def dispatch(self, request: Request, call_next):
        content_type = request.headers.get("Content-Type", "")
        file_content_type = ''

        if content_type.startswith("multipart/form-data"):
            bd = await get_body(request)
            file_content_type = get_content_type_from_body(bd)

        if file_content_type:
            for route in request.app.routes:
                try:
                    for dependency in route.dependant.dependencies:
                        if not isinstance(dependency.cache_key[0], ContentTypeChecker):
                            continue

                        valid_content_type = dependency.call(
                            content_type=file_content_type)

                        if not valid_content_type:
                            exc = HTTPException(
                                detail=f'File of type {file_content_type} not in {dependency.cache_key[0].content_types}',
                                status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)

                            return JSONResponse(status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, content={'message': exc.detail})

                except AttributeError as e:
                    if e.name == 'dependant':
                        pass

        response = await call_next(request)
        return response

然后,為了使其工作,內容類型檢查器是一個簡單的 class,它使用允許的內容類型列表和一個__call__方法實例化,該方法在中間件中接收內容類型

class ContentTypeChecker:
    def __init__(self, content_types: List[str]) -> None:
        self.content_types = content_types

    def __call__(self, content_type: str = ''):
        if content_type and content_type not in self.content_types:
            return False
        return True

這種方法的一個警告是,如果內容類型與允許的類型匹配並且中間件轉發請求,FastAPI 將再次調用它。 因此, __call__方法上 content_type 的默認值為''並在 FastAPI 自行進行檢查時返回 True 。

最后,這是路由定義:

@router.post('/upload',
             dependencies=[Depends(ContentTypeChecker(['text/csv']))]
             )
async def upload(file: UploadFile = File(...)):
    ...

我不確定是否有更好的方法來調用依賴於驗證過程。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM