如何将Base64编码图像发送到FastAPI后端
我使用this和that answer中的代码将一个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
我仅显示终结点的签名,因为目前它中没有发生任何事情。
下面是我如上所示调用后端时的输出:
简而言之,我一直收到422个错误代码,这意味着我发送的内容与端点期望的内容不匹配,但即使经过一些阅读,我仍然不清楚问题到底是什么。我们非常欢迎您的帮助!172.17.0.1:36464-选项/Check/Http/1.1&Quot;200
172.17.0.1:36464-POST/CHECK/Http/1.1307
172.17.0.1:36464-选项/检查Http/1.1200
172.17.0.1:36464-POST/Check Http/1.1&q;422
解决方案
作为previously mentioned,上传的文件作为form
数据发送。根据FastAPI documentation:
来自表单的数据通常使用";媒体类型";进行编码application/x-www-form-urlencoded
当它不包括文件时。但如果表单包含文件,则编码为
multipart/form-data
。如果您使用File
,FastAPI将知道它必须 来自正文正确部分的文件。
无论您使用什么类型,bytes
或UploadFile
,因为...
如果将路径运算函数参数的类型声明为
bytes
,FastAPI将为您读取文件,您将收到
以字节为单位的内容。
因此,422无法处理实体错误。在您的示例中,您发送的是二进制数据(使用application/octet-stream
forcontent-type
),但是您的API的终结点需要form
数据(即multipart/form-data
)。
选项1
不发送Base64编码的图像,而是按原样上传文件,要么使用here所示的HTML表单,要么使用如下所示的Java脚本。正如其他人指出的,在使用JQuery时,it is imperative that you set the contentType option to false。对于纯Java脚本,如下所示(方法为credits),手动设置content-type
时会出现上述链接中描述的类似问题;因此,最好将其省略,并强制浏览器设置它(以及强制的multipart boundary)。
服务器端:
@app.post("/upload")
async def upload(file: UploadFile = File(...)):
with open("uploaded_" + file.filename, "wb") as f:
contents = await file.read()
f.write(contents)
return file.filename
客户端:
<script type="text/javascript">
function uploadFile(){
var file = document.getElementById('fileInput').files[0];
if(file){
var xhr = new XMLHttpRequest();
var url = "/upload";
xhr.open("POST", url, true);
//xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
};
var formData = new FormData();
formData.append("file", file);
xhr.send(formData);
}
}
</script>
<input type="file" id="fileInput" name="file"><br>
<input type="button" value="Upload File" onclick="uploadFile()">
如果您希望使用Axios
库进行上传,请查看this answer。
选项2
如果您还需要上传Base64编码的图片,您可以将数据作为form
数据发送,使用application/x-www-form-urlencoded
作为content-type
;而在您的API的端点,您可以定义一个Form
字段来接收数据。下面是一个完整的工作示例,其中服务器发送、接收、解码并保存到磁盘上的Base64编码图像。对于Base64编码,在客户端使用readAsDataURL方法。请注意,文件写入磁盘是使用同步写入完成的。在需要保存多个(或大)文件的情况下,最好使用async
写入,如here所述。
app.py
from fastapi import Form, Request, FastAPI
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
import base64
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.post("/upload")
async def upload(filename: str = Form(...), filedata: str = Form(...)):
image_as_bytes = str.encode(filedata) # convert string to bytes
png_recovered = base64.b64decode(image_as_bytes) # decode base64string
with open("uploaded_" + filename, "wb") as f:
f.write(png_recovered)
return 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(/^.+,/, "");
//to prevent plus signs ('+') from being stripped out and replaced by spaces
var encodedbase64String = encodeURIComponent(base64String);
uploadFile(file.name, encodedbase64String)
}, false);
if (file) {
reader.readAsDataURL(file);
}
}
function uploadFile(filename, filedata){
var xhr = new XMLHttpRequest();
var url = "http://127.0.0.1:8000/upload";
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
};
formdata = "filename=" + filename + "&filedata=" + filedata
xhr.send(formdata);
}
</script>
<input type="file" onchange="previewFile()"><br>
<img src="" height="200" alt="Image preview...">
相关文章