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 데이터를 각각 다른 엔드포인트로 분리하여 처리
- 하나의 요청에서는 단일한 Content-Type 헤더만을 사용 가능하기 때문 (FastAPI의 제약이 아닌 HTTP 프로토콜의 제약)
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 |