FastAPI上传文件至Firebase Storage时Content-Type自动变为text/plain求助
问题:FastAPI上传文件到Firebase Storage时被存储为text/plain,修改ContentType报错
我用FastAPI开发文件上传端点,通过Postman测试,需要把文件上传到Firebase Storage,但文件总是被存为text/plain。尝试显式设置ContentType时,出现了不匹配的错误。
端点代码
import os import uuid from typing import Optional import requests from dotenv import load_dotenv from fastapi import APIRouter from fastapi import status as response_status from fastapi.encoders import jsonable_encoder from fastapi.responses import JSONResponse from firebase_admin import credentials, firestore, initialize_app, storage from starlette.datastructures import UploadFile from starlette.requests import Request @router.post("/saveEmailContent/") async def save_email_content(request: Request): # Get the email data from the request form_data = await request.form() # Get the sender, recipient, subject and body from the email sender_email_id = form_data.get('sender') recipient_email_id = form_data.get('recipient') subject = form_data.get('subject') email_body = form_data.get('body-plain') attachment_count = form_data.get('attachment-count', 0) to_update_attachment_count = attachment_count == 0 # Get the user and store ID from the recipient email ID user_ref = firestore.client().collection('emailIds').where('emailId', '==', recipient_email_id).get() if len(user_ref) == 0: return JSONResponse( status_code=response_status.HTTP_404_NOT_FOUND, content={"message": "User not found"}, ) user_id = user_ref[0].to_dict().get('userId') store_id = user_ref[0].to_dict().get('storeId') # Add the email ID to the store document in the senderIds array store_ref = firestore.client().collection('stores').document(store_id) store_ref.update({'senderIds': firestore.ArrayUnion([sender_email_id]), "updatedOn": firestore.SERVER_TIMESTAMP}) # Upload attachments to Google Cloud Storage attachment_urls = [] bucket_name = os.getenv("EMAIL_ATTACHMENT_STORAGE_BUCKET_NAME") folder_name = os.getenv("EMAIL_ATTACHMENT_STORAGE_FOLDER_NAME") for field_name, field_value in form_data.items(): if isinstance(field_value, UploadFile): # Get the attachment from the request attachment = await field_value.read() # Update attachment count if update_flag is set to True if to_update_attachment_count: attachment_count += 1 # Generate a unique filename for each attachment unique_filename = f"{folder_name}/{uuid.uuid4().hex}" # Create a storage reference with the unique filename storage_ref = storage.bucket(bucket_name).blob(unique_filename) # Set the content type based on the original attachment's content type storage_ref.content_type = field_value.content_type print(f"Content-Type: {field_value.content_type}") # Upload the attachment to Firebase Cloud Storage storage_ref.upload_from_string(attachment) # Get the public URL of the uploaded attachment attachment_url = storage_ref.public_url print(attachment_url) # Add the attachment URL to the list attachment_urls.append(attachment_url) # Combine the data in a dictionary email_data = { "senderEmailId": sender_email_id, "recipientEmailId": recipient_email_id, "subject": subject, "blurb": email_body[:25], "recepientUserId": user_id, "storeId": store_id, "attachment_count": attachment_count, } # Add the email data to the database db = firestore.client() update_time, email_ref = db.collection('emails').add(email_data) # Add the email content to the email document email_ref.collection('content').add({ 'body': email_body, "attachments": attachment_urls, }) email_data["attachments"] = attachment_urls return JSONResponse( status_code=response_status.HTTP_200_OK, content={"message": "Email content saved successfully", "emailData": email_data}, )
Postman请求

错误信息
{ "errors": [ { "message": "Content-Type specified in the upload (text/plain) does not match Content-Type specified in metadata (image/jpeg). If it was a simple upload (uploadType=media), the Content-Type was specified as a bare header. If it was a multipart upload (uploadType=multipart), then the Content-Type was specified in the second part of the multipart. If it was a resumable upload (uploadType=resumable), then the Content-Type was specified with the X-Upload-Content-Type header with the start of the resumable session.", "domain": "global", "reason": "invalid" } ] }
解决方案
问题出在upload_from_string方法的默认行为上:当使用该方法上传内容时,它默认会把请求的Content-Type设为text/plain,而你先设置了storage_ref.content_type,导致元数据和上传请求的Content-Type不匹配,触发错误。
有两种修复方式:
方式1:用upload_from_file替代upload_from_string
既然已经拿到了UploadFile对象,直接使用upload_from_file更合适,它会自动处理文件的Content-Type,不需要手动读取内容:
修改上传部分的代码:
if isinstance(field_value, UploadFile): # 不需要手动read(),直接传入文件对象 if to_update_attachment_count: attachment_count += 1 unique_filename = f"{folder_name}/{uuid.uuid4().hex}" storage_ref = storage.bucket(bucket_name).blob(unique_filename) # 直接用upload_from_file,传入文件对象和Content-Type storage_ref.upload_from_file( field_value.file, content_type=field_value.content_type ) attachment_url = storage_ref.public_url attachment_urls.append(attachment_url)
方式2:在upload_from_string中指定content_type参数
如果坚持用upload_from_string,需要在方法调用时直接传入content_type,而不是先设置storage_ref.content_type:
修改代码:
if isinstance(field_value, UploadFile): attachment = await field_value.read() if to_update_attachment_count: attachment_count += 1 unique_filename = f"{folder_name}/{uuid.uuid4().hex}" storage_ref = storage.bucket(bucket_name).blob(unique_filename) # 直接在upload_from_string里指定content_type storage_ref.upload_from_string( attachment, content_type=field_value.content_type ) attachment_url = storage_ref.public_url attachment_urls.append(attachment_url)
另外,检查Postman中的文件上传设置:确保在选择文件时,Postman自动识别了正确的Content-Type,或者手动指定对应的类型(比如图片选image/jpeg),避免前端传入错误的Content-Type值。
内容的提问来源于stack exchange,提问作者Prakhar Rathi
相关产品推荐
相关产品推荐

