Programming Language/Python

[FastAPI] 10. Form Data & Request File

LeeJaeJun 2024. 6. 20. 21:09
728x90
반응형

Form과 File

  • HTML의 form(<form></form>)은 데이터를 서버에 전송할 때 JSON과 다른 인코딩 방식을 사용하기에 Form()을 사용해서 처리
  • Form 안에서 파일 업로드를 하는 부분에 대해서는 File()이나  UploadFile() 형식으로 처리
  • 즉, HTML의 form 안에 있는 데이터를 처리할 때, 파일 형식이라면 File(), UploadFile()을 쓰고 나머지는 Form() 사용
<!DOCTYPE html>
<html>
<head>
    <title>Upload Form with File</title>
</head>
<body>
    <form action="/upload/" enctype="multipart/form-data" method="post">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username"><br><br>
        <label for="password">Password:</label>
        <input type="password" id="password" name="password"><br><br>
        <label for="file">File:</label>
        <input type="file" id="file" name="file"><br><br>
        <button type="submit">Upload</button>
    </form>
</body>
</html>
from typing import Annotated
from fastapi import FastAPI, Form, File, UploadFile

app = FastAPI()

@app.post("/upload/")
async def upload_form_and_file(
    username: Annotated[str, Form()], # 파일이 아니므로 Form()으로
    password: Annotated[str, Form()], # 파일이 아니므로 Form()으로
    file: UploadFile = File(...) # 파일이므로 File(), UploadFile()로
):
    contents = await file.read()
    return {
        "username": username,
        "password": password,
        "filename": file.filename,
        "file_content_type": file.content_type,
        "file_size": len(contents)
    }
  • Form의 인코딩 방식은 대표적으로 application/x-www-form-urlencoded 방식 multipart/form-data 방식이 있음
  • Form을 사용해서 form field를 정의하면 요청의 Content-Type 헤더를 자동으로 확인하고, 해당 인코딩 방식에 따라 데이터를 디코딩하여 파싱하여 지정된 타입으로 변환
  • Form, File, UploadFile은 Body를 상속하는 class이기에 같은 방식으로 폼 파라미터 설정 가능
from typing import Annotated

from fastapi import FastAPI, Form

app = FastAPI()

@app.post("/login/")
async def login(username: Annotated[str, Form()], password: Annotated[str, Form()]):
    return {"username": username}
  • 폼 데이터와 JSON 데이터를 동시에 수신할 수 없기에 Form과 Body를 함께 사용할 수 없음
    • 하나의 요청에서는 단일한 Content-Type 헤더만을 사용 가능하기 때문 (FastAPI의 제약이 아닌 HTTP 프로토콜의 제약)
      • 즉, 하나의 요청 안에서는 폼 데이터만 수신하던가 JSON 데이터만 수신해야 함
      • JSON데이터의 Content-Type: application/json
      • 폼 데이터의 기본 Content-Type: application/x-www-form-urlencoded
      • 파일을 포함한 폼 데이터의 Content-Type: multipart/form-data
      • multipart/form-data은 파일뿐만 아니라 일반 폼 데이터(텍스트 필드 등)도 함께 포함 가능함
        • FastAPI는 이 형식을 자동으로 처리하여, 폼 필드와 파일을 적절히 분리하고 각기 다른 파라미터로 전달 가능
        • 따라서 Form()과 File(), UploadFile()을 같이 사용할 수 있음 (이 경우, Form은 multipart/form-data 방식으로 인코딩 되어있는 것을 디코딩해서 알맞게 파싱하는 것)
      • 폼 데이터와 JSON을 동시해 수신하기 위해서는 모든 데이터를 JSON으로 통합해서 전송하고, 서버에서 JSON으로 파싱하게 하던가 폼 데이터와 JSON 데이터를 각각 다른 엔드포인트로 분리하여 처리

 

File()과 UploadFile()

@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
    return {"file_size": len(file)}

@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    contents = await file.read()
    return {"filename": file.filename, "content_type": file.content_type, "content": contents.decode("utf-8")}
  • File은 기본적으로 파일 데이터를 bytes 타입으로 받도록 설계되어 있음.
    • 파일 데이터는 바이트 데이터로 인코딩되어 전송되기 때문에, 이를 처리할 때 바이트 타입으로 받는 것이 기본 동작
    • 따라서 File을 사용해서 파일 데이터를 다른 타입으로 받는 것은 불가능
  • UploadFile 파일의 메타데이터 지원 (원본 파일명, 콘텐츠 타입 등) (File은 메타데이터 접근 불가)
    • 따라서 Upload 파일은 파일의 내용을 바이트 단위로 읽고 파일의 MIME 타입을 확인해서 다양한 파일 형식 처리 가능
  • File전체 파일 내용이 메모리에 저장, UploadFile파일이 일정 크기 이상이면 디스크에 저장
    • 따라서 File은 작은 파일에, UploadFile은 큰 타입에 적합
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename}
  • UploadFile을 사용하면 비동기적으로 파일을 읽고 쓸 수 있음

 

다중 파일 처리

  • bytes 타입으로도 처리가 가능하지만, 이 경우 모든 파일의 내용이 메모리에 저장되기 때문에 작은 파일들에 대해서만 문제없이 사용 가능
  • 일반적으로는 UploadFile 사용 (알아서 작은 파일은 메모리에 저장하고, 큰 파일은 디스크에 저장)
# File() 사용하는 경우
from fastapi import FastAPI, File
from typing import List, Annotated

app = FastAPI()

@app.post("/uploadfiles/")
async def create_upload_files(files: Annotated[List[bytes], File()]):
    results = []
    for file in files:
        results.append({
            "file_size": len(file)
        })
    return results
# UploadFile 사용하는 경우
from fastapi import FastAPI, UploadFile, File
from typing import List

app = FastAPI()

@app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile]):
    results = []
    for file in files:
        contents = await file.read()
        results.append({
            "filename": file.filename,
            "content_type": file.content_type,
            "file_size": len(contents)
        })
    return results

 

UploadFile 속성과 메서드

  • 속성
    • filename: 업로드된 파일의 원본 파일명
    • content_type: 파일의 MIME 타입
    • file: SpooledTemporaryFile 객체로, 실제 파일 객체
  • 메서드
    • write(data): 파일에 데이터를 씀
    • read(size): 파일에서 데이터를 읽음
    • seek(offset): 파일 포인터를 특정 위치로 이동
    • close(): 파일을 닫음

 

application/x-www-form-urlencoded

  • 가장 일반적인 폼 데이터 인코딩 방식 (form 안에 file 형식의 input이 없는 경우)
  • 모든 문자는 키-값 쌍으로 인코딩되며, 각 쌍은 &로 구분되고, 키와 값은 =로 구분
<form action="/submit" method="post">
  <input type="text" name="username" value="john">
  <input type="text" name="password" value="secret">
  <button type="submit">Submit</button>
</form>
  • 위와 같이 일반적인 폼 데이터는 다음과 같이 인코딩
    • username=john&password=secret

 

multipart/form-data

  • 폼 데이터에 파일이 포함될 때 사용
  • 데이터를 여러 부분으로 나누어 각각의 부분에 파일이나 텍스트 데이터를 포함 가능
  • 각 부분은 고유한 경계를 가짐
<form action="/upload" method="post" enctype="multipart/form-data">
  <input type="text" name="username" value="john">
  <input type="file" name="file">
  <button type="submit">Submit</button>
</form>
  • 위와 같이파일이 포함된 폼 데이터는 다음과 같이 인코딩
--boundary
Content-Disposition: form-data; name="username"

john
--boundary
Content-Disposition: form-data; name="file"; filename="example.txt"
Content-Type: text/plain

(file content here)
--boundary--

 

 

 

 

728x90
반응형

'Programming Language > Python' 카테고리의 다른 글

[FastAPI] 12. Update: PUT, PATCH  (0) 2024.06.20
[FastAPI] 11. JSON 호환 인코더: jsonable_encoder  (0) 2024.06.20
[FastAPI] 9. Extra Data Types  (0) 2024.06.19
[FastAPI] 8. Fields  (0) 2024.06.19
[FastAPI] 7. Body Parameter  (0) 2024.06.19