FastAPI - Uploading multiple files using Axios raises Bad Request error - javascript

Client code:
!<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<form id="uploadForm" role="form" method="post" enctype=multipart/form-data>
<input type="file" id="file" name="file" multiple>
<input type=button value=Upload onclick="uploadFile()">
</form>
<script type="text/javascript">
function uploadFile() {
var formData = new FormData();
var imagefile = document.querySelector('#file');
formData.append("images", imagefile.files);
axios.post('http://127.0.0.1:8000/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
</script>
</body>
</html>
Server code:
from fastapi import FastAPI, File, UploadFile, FastAPI
from typing import Optional, List
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
...
def save_file(filename, data):
with open(filename, 'wb') as f:
f.write(data)
print('file saved')
#app.post("/upload")
async def upload(files: List[UploadFile] = File(...)):
print(files)
for file in files:
contents = await file.read()
save_file(file.filename, contents)
print('file received')
return {"Uploaded Filenames": [file.filename for file in files]}
I get the following error:
←[32mINFO←[0m: 127.0.0.1:10406 - "←[1mPOST /upload HTTP/1.1←[0m" ←[31m400 Bad Request←[0m
I have tried to upload a single file via form action and all works fine, but I need to upload two files.

First, when uploading files or form data, one should use the same form key defined in their endpoint/route. In your case, that is files. Hence, on client side you should use that key instead of images.
Second, the way to upload multiple files is to loop through the array of files (i.e., imagefile.files in your code) and add each file to the FormData object.
Third, the error seems to occur due to some bug/changes in the 0.27.1 version of Axios. Here is a recent related issue on GitHub. Using the latest version, i.e., 0.27.2, from cdnjs here resolves the issue.
Alternatively, you could use Fetch API, similar to this answer (you can add the list of files in the same way as shown in the Axios example below). In Fetch API, when uploading files to the server, you should not explicitly set the Content-Type header on the request, as explained here.
Working Example (using Axios)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Upload Files</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>
</head>
<body>
<input type="file" id="fileInput" multiple><br>
<input type="button" value="Upload" onclick="uploadFile()">
<script type="text/javascript">
function uploadFile() {
var fileInput = document.querySelector('#fileInput');
if (fileInput.files[0]) {
var formData = new FormData();
for (const file of fileInput.files)
formData.append('files', file);
axios({
method: 'post',
url: '/upload',
data: formData,
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data'
}
})
.then(response => {
console.log(response);
})
.catch(error => {
console.error(error);
});
}
}
</script>
</body>
</html>

Starting from Axios v 0.27.2 you can do this easily:
axios
.postForm("https://httpbin.org/post", document.querySelector("#fileInput").files)
All the files will be submitted with files[] key.
More verbose example:
axios.postForm("https://httpbin.org/post", {
"myField": "foo"
"myJson{}": {x:1, y: 'bar'},
"files[]": document.querySelector("#fileInput").files
})

Related

Upload image from React Native to ASP.NET Core Web Api

I'm using react-image-picker to take pictures and get the URI but I'm having problems receiving images as an IFormFile in my API. When using the "Content-Type": "multipart/form-data" header my array is null. Without it the array is empty but the data is there as a key-value pair the value is of type object inside HttpContext.Request.Form.
I've tried using both axios and fetch. I've tried sending the object as a blob with the content-type specified as image/jpeg, I've tried stringifying the Asset object but my request doesn't show a content-type for the FormData no matter what.
I tried creating a quick Vue project and the request goes through to my API as a file there without any issues.
It seems like the difference comes down to whether the FormData has a content-type specified. on Vue, it looks like
------WebKitFormBoundaryQoBY2Xgnc8K2sTzQ
Content-Disposition: form-data; name="photos"; filename="2560px-Random_Turtle.jpg"
Content-Type: image/jpeg
while in react it looks like. It looks like it even ignores the filename that's set in the append.
------WebKitFormBoundaryQoBY2Xgnc8K2sTzQ
Content-Disposition: form-data; name="photos"
Is there any way to force the content-type in the FormData? Using Blob like this doesn't change the request in any way
data.append("photos", new Blob([JSON.stringify(photo)], {type: "image/jpeg"}), "2560px-Random_Turtle")
React Native (Not working)
export const TestWithPhotosAPI = async (photos: Asset[]) => {
let data = new FormData();
if (containerAndPhotos.photos) {
containerAndPhotos.photos.forEach((photo) => {
data.append("photos", photo as Blob, "2560px-Random_Turtle");
});
}
fetch("https://myapi/postpicture", {
method: "POST",
body: data,
});
};
Vue project (Works)
<script setup lang="ts">
import { ref } from 'vue'
const form = ref(null)
const files = ref<FormData>(new FormData)
const filesChange = (event ) => {
console.log(event.target.files);
Array.from(event.target.files).map((file, index) => {
console.log(file);
files.value.append("photos", file as Blob, event.target.files[index].name)
})
}
const upload = () => {
fetch(
'https://myapi/postpicture',
{
method: "POST",
body: files.value
}
);
}
</script>
<template>
<form ref="form" #submit.prevent="upload">
<input type="file" name="file" multiple #change="(event) => filesChange(event)" />
<input type="submit" />
</form>
</template>
API (Works with Vue)
[HttpPost]
public ActionResult SetAsDone([FromForm] ICollection<IFormFile> photos)
{
var httpRequest = HttpContext.Request.Form;
return Ok();
}

How to upload a file from Vue frontend to FastAPI backend

Vuejs Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="file-upload-list" class="container">
<div class="large-12 medium-12 small-12 cell">
<label>File
<input type="file" id="file" ref="file" v-on:change="handleFileUpload()"/>
</label>
<button v-on:click="submitFile()">Submit</button>
</div>
</div>
<script>
var fileList = new Vue({
data(){
return {
file: ''
}
},
el: "#file-upload-list",
methods: {
/*
Submits the file to the server
*/
submitFile(){
/*
Initialize the form data
*/
let formData = new FormData();
/*
Add the form data we need to submit
*/
formData.append('file', this.file);
console.log(this.file);
var baseURL="http://127.0.0.1:8000"
/*
Make the request to the POST /single-file URL
*/
axios.post( baseURL+'/uploadfiles',
// formData,
{
headers: {
'Content-Type': 'multipart/form-data'
},
file:this.file
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
console.log(formData);
},
/*
Handles a change on the file upload
*/
handleFileUpload(){
this.file = this.$refs.file.files[0];
}
}
});
</script>
</body>
</html>
FastAPI code
#app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile] = File(...)):
result=""
for file in files:
fpath = os.path.join(
STORAGE_PATH, f'{file.filename}'
)
async with aiofiles.open(fpath, 'wb') as f:
content = await file.read()
await f.write(content)
return {"message": "success"}
I am trying to upload a pdf file from vuejs to fastAPI. where pdf file will be saved in a particular location in fastAPI code mentioned. I have tested the fastapi code in postman and it works good and I also enabled cross-origin. I am new to vuejs and I don't know how to do form action functionality. Mainly I need to know HTTPFile transaction is happening in the vuejs

Returning HTML with Azure Serverless Function req.body

I've got some TIF files in Azure Blob Storage. I'd like to display them in the browser via a link embedded in a spreadsheet. The simplest way to do this should be to take the file code as a request parameter and return the properly formatted HTML, right?
So right now I've got it returning a req.body with some HTML. Unfortunately, the HTML just shows up as a string in the browser. How do I make it render as HTML with minimal rigamarole?
Here's my code:
if (req.query.blob) {
let blob = req.query.blob;
context.res = {
// status: 200, /* Defaults to 200 */
body: `<object width=200 height=200
data="<baseaddress>/${blob}.tif" type="image/tiff">
<param name="src" value="<baseaddress>/${blob}.tif">
<param name="negative" value="yes">
</object>`
};
}
You need to set the headers to specify the content type to HTML and the response must be a full valid HTML page (with the <html> tag and the rest).
Example:
module.exports.hello = (event, context, callback) => {
const html = `
<!doctype html>
<html>
<head>
<title>The Page Title</title>
</head>
<body>
<h1>Hello</h1>
</body>
</html>`;
const response = {
statusCode: 200,
headers: {
'Content-Type': 'text/html'
},
body: html
};
callback(null, response);
};

Sending a CSV file using Node/Express

I'm building a front end tool that allows users to upload a CSV file to a certain end point. I use the HTML5 input="file"
Here is my JSX, I use the React Dropzone library:
<Dropzone
multiple={false}
onDrop={this.onDrop}
>
<div>
<input type="file" />
</div>
</Dropzone>
The onDrop function:
onDrop(acceptedFile) {
this.setState({ file: acceptedFile });
}
I grab that file and put it on my state. My submit function looks like this:
submitFile() {
const formData = new FormData();
formData.append('uploadCsv', this.state.file);
fetch('/api/person/uploadfile', {
body: formData,
method: 'POST'
});
}
It then goes through a few files for routing..
router.use('/person', require('./routers/personRoutes').default);
router.post('/uploadfile', fileUpload);
Then I run my fileUpload function:
export const fileUpload = post({
route: req => {
return `${BASE_URL}/v1/fileupload/persons`;
}
});
Here I have no idea what to do. Usually I just have to return a URL and everything works. req.body is just an empty object - {}. The error message I get back says:
Current request is not a multipart request
BUT. Something as simple as this does work:
<html>
<form action="http://localhost:9275/v1/fileupload/persons"
enctype="multipart/form-data" method="post">
<p>
Please specify a file, or a set of files:<br>
<input type="file" name="file" size="40">
</p>
<div>
<input type="submit" value="Send">
</div>
</form>
</html>
I've looked all over stack overflow and none of solutions have helped thus far :( the coworker who wrote the node stuff left so I'm alone on this

CherryPy and CORS

I have an nginx server set up where I would like to set up a service that receives a string and returns a result. I plan to use Python to do the processing, with CherryPy as an interface. I've tested the CherryPy part, and know it receives properly. When I try to connect to the CherryPy service with a web page, I get CORS errors. How can I get them to communicate?
Here's the Python Code:
import cherrypy
import random
import urllib
class DataView(object):
exposed = True
#cherrypy.tools.accept(media='application/json')
def GET(self):
rawData = cherrypy.request.body.read(int(cherrypy.request.headers['Content-Length']))
b = json.loads(rawData)
return json.dumps({'x': 4, 'c': b})
def CORS():
cherrypy.response.headers["Access-Control-Allow-Origin"] = "*"
if __name__ == '__main__':
conf = {
'/': {
'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
'tools.CORS.on': True,
}
}
cherrypy.tools.CORS = cherrypy.Tool('before_handler', CORS)
cherrypy.config.update({'server.socket_port': 3000})
cherrypy.quickstart(DataView(), '', conf)
Here's my web page:
<html lang="en">
<head>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<link href="http://code.jquery.com/ui/1.10.4/themes/ui-lightness/jquery-ui.css" rel="stylesheet">
<script type="text/javascript">
$(document).on('click', "#submitButton", function(){
$.ajax({
type: 'GET',
url: 'http://localhost:3000',
contentType: 'text/plain',
xhrFields: {
// The 'xhrFields' property sets additional fields on the XMLHttpRequest.
// This can be used to set the 'withCredentials' property.
// Set the value to 'true' if you'd like to pass cookies to the server.
// If this is enabled, your server must respond with the header
// 'Access-Control-Allow-Credentials: true'.
withCredentials: false
},
headers: {
},
success: function() {
console.log("Success");
},
error: function() {
console.log("Fail");
}
});
});
</script>
</head>
<body>
<div id="header">
<h2>PDE Grammar Engine</h2>
<form>
Input Sentence:<br>
<input type="text" name="query" id="query"><br>
<input type="submit" id="submitButton" value="Submit">
</form>
</div>
</div id="results">
</div>
</body>
</html>
Turned out that the CherryPy server was not actually listening to the correct address. It was allowing connections from localhost, but not external connections. I had to add the following entry to the cherrypy.config.update
cherrypy.config.update({'server.socket_host': '0.0.0.0',
'server.socket_port': 3000})

Categories

Resources