如何将 base64 编码的图像发送到 FastAPI 后端?
- 2025-02-11 09:51:00
- admin 原创
- 64
问题描述:
我正在使用此答案和该答案中的代码将 base64 编码的图像发送到 python FastAPI 后端。
客户端如下所示:
function toDataURL(src, callback, outputFormat) {
var img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = function() {
var canvas = document.createElement('CANVAS');
var ctx = canvas.getContext('2d');
var dataURL;
canvas.height = this.naturalHeight;
canvas.width = this.naturalWidth;
ctx.drawImage(this, 0, 0);
dataURL = canvas.toDataURL(outputFormat);
callback(dataURL);
};
img.src = src;
if (img.complete || img.complete === undefined) {
img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
img.src = src;
}
}
function makeBlob(dataURL) {
var BASE64_MARKER = ';base64,';
if (dataURL.indexOf(BASE64_MARKER) == -1) {
var parts = dataURL.split(',');
var contentType = parts[0].split(':')[1];
var raw = decodeURIComponent(parts[1]);
return new Blob([raw], { type: contentType });
}
var parts = dataURL.split(BASE64_MARKER);
var contentType = parts[0].split(':')[1];
var raw = window.atob(parts[1]);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], { type: contentType });
}
...
toDataURL(
images[0], // images is an array of paths to images
function(dataUrl) {
console.log('RESULT:', dataUrl);
$.ajax({
url: "http://0.0.0.0:8000/check/",
type: 'POST',
processData: false,
contentType: 'application/octet-stream',
data: makeBlob(dataUrl)
}).done(function(data) {console.log("success");}).fail(function() {console.log("error");});
}
);
服务器端如下:
@app.post("/check")
async def check(file: bytes = File(...)) -> Any:
// do something here
我只显示端点的签名,因为目前为止,其中没有发生太多事情。
下面是我按上面方法调用后端时的输出:
172.17.0.1:36464 - “选项/检查/HTTP/1.1” 200
172.17.0.1:36464 - “POST /检查/ HTTP/1.1” 307
172.17.0.1:36464 - “选项/检查 HTTP/1.1” 200
172.17.0.1:36464 - “POST /检查 HTTP/1.1” 422
简而言之,我不断收到422 error
代码,这意味着我发送的内容与端点期望的内容不匹配,但即使读了一些资料,我仍然不清楚问题到底是什么。任何帮助都欢迎!
解决方案 1:
如本答案所述,上传的文件将作为数据发送form
。 根据FastAPI 文档:
application/x-www-form-urlencoded
当表单中的数据不包含文件时,通常使用“媒体类型”进行编码
。但是当表单包含文件时,它被编码为
multipart/form-data
。如果你使用File
,FastAPI 就会知道它必须从正文的正确部分获取文件。
无论您使用哪种类型,都bytes
可以UploadFile
,因为...
如果您将路径操作函数参数的类型声明为
bytes
,则FastAPI 将为您读取文件,并且您将以字节形式接收内容。
因此,422 Unprocessable entity
出现错误。在您的示例中,您发送二进制数据(使用application/octet-stream
for content-type
),但是,您的 API 端点需要form
数据(即multipart/form-data
)。
选项 1
不要发送 base64 编码的图像,file
而是使用 HTML (如此处form
所示)或 Javascript (如下所示)按原样上传。正如其他人指出的那样,使用 JQuery 时,必须将 contentType 选项设置为 false 。使用Fetch API(如下所示),最好也将其省略,并强制浏览器设置它(以及强制性的多部分边界)。有关FastAPI 后端的读/写,请查看此答案。async
应用程序.py:
import uvicorn
from fastapi import File, UploadFile, Request, FastAPI, HTTPException
from fastapi.templating import Jinja2Templates
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.post("/upload")
def upload(file: UploadFile = File(...)):
try:
contents = file.file.read()
with open("uploaded_" + file.filename, "wb") as f:
f.write(contents)
except Exception:
raise HTTPException(status_code=500, detail='Something went wrong')
finally:
file.file.close()
return {"message": f"Successfuly uploaded {file.filename}"}
@app.get("/")
def main(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
模板/index.html
<script>
function uploadFile(){
var file = document.getElementById('fileInput').files[0];
if(file){
var formData = new FormData();
formData.append('file', file);
fetch('/upload', {
method: 'POST',
body: formData,
})
.then(response => {
console.log(response);
})
.catch(error => {
console.error(error);
});
}
}
</script>
<input type="file" id="fileInput" name="file"><br>
<input type="button" value="Upload File" onclick="uploadFile()">
如果您想使用Axios
库进行上传,请查看这个答案。
选项 2
如果您仍需要上传 base64 编码的图像,则可以使用data发送数据form
,并在 API 端点中定义一个字段来接收数据。下面是一个完整的工作示例,其中发送了 base64 编码的图像,服务器接收该图像,对其进行解码并保存到磁盘。对于 base64 编码,客户端使用readAsDataURL方法。请注意,文件写入磁盘是使用同步写入完成的。在需要保存多个(或大)文件的场景中,最好使用写入,如此处所述。application/x-www-form-urlencoded
`content-typeForm
async`
应用程序
from fastapi import Form, Request, FastAPI
from fastapi.templating import Jinja2Templates
import base64
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.post("/upload")
def upload(filename: str = Form(...), filedata: str = Form(...)):
image_as_bytes = str.encode(filedata) # convert string to bytes
img_recovered = base64.b64decode(image_as_bytes) # decode base64string
with open("uploaded_" + filename, "wb") as f:
f.write(img_recovered)
return {"message": f"Successfuly uploaded {filename}"}
@app.get("/")
def main(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
模板/index.html
<script type="text/javascript">
function previewFile() {
const preview = document.querySelector('img');
const file = document.querySelector('input[type=file]').files[0];
const reader = new FileReader();
reader.addEventListener("load", function () {
preview.src = reader.result; //show image in <img tag>
base64String = reader.result.replace("data:", "").replace(/^.+,/, "");
uploadFile(file.name, base64String)
}, false);
if (file) {
reader.readAsDataURL(file);
}
}
function uploadFile(filename, filedata){
var formData = new FormData();
formData.append("filename", filename);
formData.append("filedata", filedata);
fetch('/upload', {
method: 'POST',
body: formData,
})
.then(response => {
console.log(response);
})
.catch(error => {
console.error(error);
});
}
</script>
<input type="file" onchange="previewFile()"><br>
<img src="" height="200" alt="Image preview...">