POST 400 (BAD REQUEST) when uploading file between React and Flask - reactjs

Keep getting a 400 error when uploading an excel file in my react front end and passing it to a flask api.
The flask backend route is as such:
def post(self):
name = request.form['name']
file = request.files['file']
(....do stuff with file and name)
The react API call is as follows:
export const uploadFile = async (file) => {
const data = new FormData()
data.append("file", file)
data.append("name", 'temp')
const api_url = "http://localhost:5000/uploadFile"
const settings = {
method: "POST",
body: data
}
try {
const response = await fetch(api_url, settings)
const result = await response.json()
if (result.message === 'OK') {
return result
}
} catch (error) {
return "Unable to load file"
}
}
Interesting the NAME field is getting received by Flask but the file object for some reason is not being processed by Flask.
I've tried adding a multi-part/formdata Content-Type in the React Fetch API call as well, but that doesn't help either.

Figured out the issue.
Turns out you need to pass the [0] following in the React call:
data.append("file", file[0])

I was trying to post an image file to Flask just using a vanilla JS fetch call, and #Anubhav's answer helped me realise that I needed to use FormData, rather than just posting the file/blob as the body. In case someone is looking for some full, working example code:
<input type="file" id="fileEl"/>
let formData = new FormData();
formData.append("file", fileEl.files[0]);
let response = await fetch("http://example.com/upload", {
method: "POST",
body: formData,
}).then(r => r.json());

Related

Error sending PDF from ReactJS to Flask app

I'm trying to send a PDF from a frontend react app to a backend flask app using fetch. The idea is a user uploads a PDF on the frontend and sends to a backend that parses it and returns an edited version in JSON format.
Frontend:
function App() {
const [file, setFile] = useState(null);
const [data, setData] = useState();
const upload = (event) => {
setFile(event.target.files[0]);
};
const submit = () => {
const formData = new FormData();
formData.append("file", file);
fetch("http://localhost:5000/parser", {
method: "POST",
mode: "no-cors",
body: formData,
})
.then((res) => res.json())
.then((res) => {
setData(res);
console.log(res);
});
};
useEffect(() => {
if (file) {
submit();
}
}, [file]);
return (
<div className="App">
{" "}
<input type="file" accept="application/pdf" onChange={upload} />
<input type="submit" onClick={submit} />
</div>
);
}
export default App;
Backend:
app = Flask(__name__)
CORS(app)
#app.route("/parser", methods=['GET', 'POST'])
def parse():
file = request.files['file']
file.save(secure_filename("secure.pdf"))
fp = open("secure.pdf", 'rb')
data = convert(fp, 0)
response = make_response(data)
response.headers["Content-Type"] = "application/json"
response.headers["Access-Control-Allow-Origin"] = "http://localhost:5173"
return response
if __name__ == "__main__":
# for production change debug to False
app.run(debug=True)
The flask server is running with this error:
werkzeug.exceptions.BadRequestKeyError: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.
KeyError: 'file'
I'm not sure what I'm doing wrong here. As of now when I hit submit on the front end to send the PDF, the backend saves it to the local repo. So the PDF is getting through but for some reason there's an issue on the way back it seems.
Is there a better way to do this? To send a PDF and return a JSON? The python function I have works when I run it with a local PDF in the dir, but I need these two apps to work dynamically with each other.
The error makes even less sense to me because I'm able to save the file to the local repo, but am still getting an error seemingly on that line: file = request.files['file']
You cannot send the pdf as a json file until it get transformed. You should change it to b64 kind of transformation as temporary file(encoding and decoding) like this.

sending more data along with formData to an API with axios

I´m writing an API that loads files in a folder
In order to select the files I’m using FormData. I use
<input type='file' ref={inputElement} onChange={handleChange} />
After choosing the file, In use axios to make a request
const uploadFile = () => {
const formData = new FormData();
formData.append('file', file); // appending file
axios
.post('http://localhost:3001/upload', formData, {…
The api receives it and does it’s thing
app.post('/upload', (req, res) => {
if (!req.files) {
return res.status(500).send({ msg: 'file not specified' });
}
// accessing the file
const myFile = req.files.file;
It works fine.
But, I’d like to send extra info to the endpoint, so, I send the extra info and the formdata to axios:
const uploadFile = () => {
const formData = new FormData();
formData.append('file', file); // appending file
axios
.post('http://localhost:3001/upload', {data: formData, extraInfo: 'more info'}, {
And in the endpoint I write:
app.post('/upload', (req, res) => {
console.log(req.body.extraInfo)
console.log(req.body.data)
extraInfo 'more info', ok, but data is empty, I supposed that data should contain formdata, but it’s empty, what can I do in order to get the formData and the extraInfo at the same time
Thanks in advance
Rafael
Just add the extraInfo to the formData and then send it to your server. You may need to double check how your server wants to get the data.
const uploadFile = () => {
const formData = new FormData();
formData.append('file', file); // appending file
formData.append('extraInfo', "Some Info");// additonal data
axios
.post('http://localhost:3001/upload', formData, {

Data corrupts when uploading to S3 (PUT) using Axios in React

With the following function, I am able to upload images to S3, but I am unable to open the image when I attempt to retrieve it. The following is a function used in a React application that handles the file using 'react-dropzone':
import axios, { post } from 'axios'
import { Buffer } from 'buffer'
const processImage = ({ file }) => {
const reader = new FileReader()
reader.onload = async () => {
const base64data = reader.result
const bufferdata = Buffer.from(base64data, 'base64')
const generateSignedS3Url = '/.netlify/functions/get-s3-urls'
// Obtains signed URLs from a Netlify Serverless Function
const { data } = await post(generateSignedS3Url, {
clientFilename: file.name,
mimeType: file.type,
}).catch(e => {
console.error('Error in generating signed url ', e)
})
axios({
method: 'PUT',
url: data.putUrl,
data: bufferdata,
headers: { 'Content-Type': file.type, 'Content-Encoding': 'base64' },
}).catch(e => {
console.error(e)
})
}
reader.readAsDataURL(file)
}
The following contains the parameters used to obtain the signed URL for the serverless function invoked above:
const putParams = {
Bucket,
Key,
Expires: 2 * 60,
ContentType: body.mimeType,
ContentEncoding: 'base64',
}
const putUrl = s3.getSignedUrl('putObject', putParams)
Here, body.mimeType would be 'image/jpeg'.
Scanning from similar Stack Overflow questions, I've ensured the following:
The header 'Content-Encoding' is set to 'base64'
I am using a buffer to send the content using the NPM buffer library (this is a client application)
The header 'Content-Type' matches the file that is being sent up (ie for a file "background.jpg", the header is set to 'image/jpeg')
I've also attempted to send without a buffer
I've looked through similar SO questions before submitting. They include 1, 2, 3. There are other ones, but they are for the Node backend.
reader.result has a prefix before the encoded data. As described on MDN:
The blob's result cannot be directly decoded as Base64 without first removing the Data-URL declaration preceding the Base64-encoded data. To retrieve only the Base64 encoded string, first remove data:*/*;base64, from the result.
So you can do something like:
const base64data = reader.result.split(',')[1];

How to send files to Django REST Framework from React?

I need to send arbitrary (e.g. xls) files from React-based frontend to Django REST Framework backend.
Googled and tried many code variants for couple of hours, none of them worked completely.
Here are essential parts of code:
React
1.1 Form input field
<input
type="file"
multiple={true}
accept=".xls,.xlsx,.csv,.txt"
onChange={this.handleFilesChosen}
/>
1.2 handleFilesChosen
handleFilesChosen = event => {
this.setState({
files: event.target.files
});
}
1.3 Upload click handler (authHeader is function substituting Authorization Bearer token)
handleUploadClick = event => {
let formData = new FormData();
for (let file of this.state.files) {
formData.append('files', file);
}
const csrf = this.getCookie('csrftoken');
fetch(`${API_BASE_PATH}/load-input-data/`, {
method: 'POST',
headers: authHeader({contentType: 'multipart/form-data', csrf: csrf}),
body: formData,
})
.then(result => result.json())
.catch(error => error);
}
DRF View
class LoadInputDataView(APIView):
parser_class = (MultiPartParser,)
#method_decorator(login_required)
def post(self, request, format=None):
print(request.data)
return Response(status=status.HTTP_201_CREATED)
I selected simple txt file (to make debugging easy, binary will go later) with hello world content, uploaded it and get <QueryDict: {}> in Django runserver console.
If I look at Chrome network tab, I see following empty request payload instead of real file content:
------WebKitFormBoundaryYw6ABRFkvxatzHqi
Content-Disposition: form-data; name="files"; filename="foo.txt"
Content-Type: text/plain
------WebKitFormBoundaryYw6ABRFkvxatzHqi--
Tried to remove contentType header - got 400 error with message JSON parse error (browser substitutes JSON contentType header automatically).
I'm stuck. Could anybody guide me?
Found solution. I should not set Content-Type header manually, it is set automatically with boundary option. Now Django's request.FILES work too and I could work with uploaded files from backend using code like:
class ParseInputDataView(APIView):
parser_class = (MultiPartParser,)
permission_classes = [permissions.IsAuthenticated]
def post(self, request, controller_id, format=None):
for file_entry in request.FILES.getlist('files'):
uploaded_file_name = file_entry.name
uploaded_file_content = file_entry.read()
...
I decided to maintain uniformity in the API and send the image within JSON.
In React:
const [image, setImage] = useState(null);
const handleImageChange = (e) => {
e.preventDefault();
const reader = new FileReader();
reader.onload = () => {
var blocks = reader.result.split(";");
const realData = blocks[1].split(",")[1];
setImage(realData);
};
reader.onerror = (error) => console.error(error);
reader.readAsDataURL(e.target.files[0]);
};
const onSaveHandler = () => {
fetch(`/url`, {
method: "post",
credentials: "include", // send cookie with auth
headers: {
"Content-Type": "application/json",
"X-CSRFToken": document.getElementById("csrf-token").value,
}
body: JSON.stringify({imageData: image}),
});
}
return(
<div>
<input
onChange={handleImageChange}
id="logo"
type="file"
multiple="false"
accept="image/*"
/>
<button onClick={onSaveHandler}>
SAVE
</button>
</div>);
In Django (DRF):
class CustomerViewSet(viewsets.ModelViewSet):
# override create method
def create(self, request, *args, **kwargs):
image_path = "whatever.jpg"
print('save image on disk: ' + image_path)
with open(image_path, "wb") as fh:
fh.write(base64.b64decode(request.data.get("imageData")))
return super().create(request)

fetching a plain/text with redux-api-middleware

Can I fetch a txt file with redux-api-middleware? If yes, how? According to the doc the body is set to undefined if not application/JSON compliant, but what I will get is a simple text/plain
Basically what I did was altering fetch action and doing my custom stuff with response data. Example:
fetch: async (...args) => {
const response = await fetch(...args);
if (response.ok && response.status === 200) {
const reader = response.body.getReader();
const type = response.headers.get('content-type');
const filename = filenameExtractor(response.headers.get('content-disposition'));
const { value } = await reader.read();
fileSaver({ value, filename, type });
}
return response;
},
This piece of code reads response contents and pushes them for download to the browser. filenameExtractor and fileSaver are custom functions. This way response is not altered.
Using such approach you can further modify response and create json yourself for data to be able to be saved in redux like:
const response = await fetch(...args);
return new Response(
JSON.stringify({ data: response.body })
);

Resources