JS client doesn't read response when server aborts file upload - javascript

We have an API (Spring Boot) for file uploads. If file exists it returns 409.
But js client doesn't read this response and fails with error "net::ERR_CONNECTION_ABORTED".
From wireshark dump I see that the backend sends the response and then closes the connection:
I think the problem is that js client doesn't read the response as soon as it available.
However, Postman is able to read the response correctly.
Is it possible to implement Postman's behavior in javascript?
I've tried to read response stream with Fetch API, but no luck.
Sample client code:
function uploadFile() {
try {
console.log("Start file upload");
const selectedFiles = document.getElementById('input').files;
if (selectedFiles.length == 0) {
alert("No file selected");
return;
}
const file = selectedFiles[0];
return fetch("http://localhost:9091/api/v1/storage/upload?fileId=/upload-test/test.mp4", {
method: 'PUT',
body: file,
headers: {
'Content-Type': 'application/octet-stream'
},
})
.then(response => {
console.log("Processing response");
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
return pump();
function pump() {
return reader.read().then(({ value, done }) => {
if (done) {
console.log("Finished stream reading");
return;
}
console.log(value);
return pump();
});
}
})
.catch((err) => console.error("error:", err));
} catch (error) {
console.log(error);
}
}

Related

How to delete zip file after sent response in express

I just want to delete zip folder after sent response so I am looking for any alternative solution
Here is my code / it is get request
exports.Download = async (req, res) => {
try {
var zp = new admz();
zp.addLocalFolder(`./download/${folder}`);
const file_after_download = 'downloaded.zip';
const data = zp.toBuffer();
res.set('Content-Type', 'application/octet-stream');
res.set('Content-Disposition', `attachment; filename=${file_after_download}`);
res.set('Content-Length', data.length);
return res.send(data);
// HERE I want execute this code
let dir = `./download/${folder}`;
if (fse.existsSync(dir)) {
fse.rmdirSync(dir, { recursive: true })
}
} catch (err) {
console.log(err)
return res.render('pages/404');
}
}
Update
If send code without return ( res.send(data);)
Im getting this error //Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client //
If I put return res.send(data); at the end of block , then downloaded zip file will be empty - because its deleted already
From the docs of Express, you can use res.download() function which has a callback parameters to be executed once download is done.
res.download(filePath, 'yourFileName', function(err) {
if (err) {
next(err)
} else {
console.log('Delete:', filePath);
}
})

Prevent a client from waiting indefinitely for server response

I have a scenario where a file is uploaded in the web application and is sent to a back end server to upload in storage. Till a response is received from the server a loading spinner is shown. In case the server is down after receiving the request, no response is received by the client and the web page still shows that the file upload is still in progress. Is there a way to check if the server is not responding and then show an error message to the user ?
export const upload = async (file) => {
let result = null;
try {
const formData = new FormData();
formData.append("file", file, file.name);
result = await API.post(
"/fileupload",
formData,
{}
);
if (result.status === 200) {
return result.data;
} else {
return "Upload file failed.";
}
} catch (ex) {
console.log("parsing failed", ex);
}
};
You can do this with a timeout. Ideally the library you're using allows you to set a timeout, but if not you can manually create a timeout using Promise.race
function timeout(ms) {
return new Promise(reject => setTimeout(() => reject(new Error('timeout'), ms));
}
export const upload = async (file) => {
let result = null;
try {
const formData = new FormData();
formData.append("file", file, file.name);
result = await Promise.race([
timeout(30000), // 30 seconds
API.post(
"/fileupload",
formData,
{}
),
]);
if (result.status === 200) {
return result.data;
} else {
return "Upload file failed.";
}
} catch (ex) {
if (ex.message === 'timeout') {
// timeout handling
}
console.log("parsing failed", ex);
}
};
Note that this can be pretty fragile and ideally you would use a library to handle uploads.
Ideally the timeout handling should be added at the API.post module level

Receving "500 Internal Server Error" on Post Request to Firebase-Cloud-Function Endpoint

I'm trying to make a POST request using axios to my firebase cloud-function on form submit in react app. But I get '500' error everytime I make a request with an html-page response This app works best with javascriot enabled.
Latest Update:
It looks like there is no issue with cloud function
code. Rather more of a react-component issue. I used Postman to send
the POST request with header prop Content-Type set to application/json
and sending body in raw format {"email": "example_email"} and got
expected response from the cloud function. But when sent the request from
react component above, I get an html file response saying the app
works best with javascript enabled
I've tried setting Content-Type to both Application/json and multipart/form-data as I suspected it to be an issue but still got no luck.
Following is my code for cloud function and react submit form:
Cloud Function
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true })
const runThisFunc1 = require(./libs/runThisFunc1);
const runThisFunc2 = require(./libs/runThisFunc2);
exports.wizardFunc = functions.https.onRequest((request, response) => {
cors(request, response, () => {
let email = request.body.email;
try {
return runThisFunc1(email)
.then(data => {
console.log("Word Done by 1!");
return runThisFunc2(data);
})
.then(res => {
console.log("Word Done by 2!");
return response.status(200).send("Success");
})
.catch(err => {
console.error("Error: ", err.code);
return response.status(500).end();
});
}catch(err) {
return response.status(400).end();
}
});
});
React-Form-Component Snippet
import axios from 'axios'
...
handleSubmit = e => {
e.preventDefault()
const { email } = this.state
axios({
method: 'post',
url: `${process.env.REACT_APP_CLOUD_FUNCTION_ENDPOINT}`,
data: { email: email },
config: {
headers: {
'Content-Type': 'multipart/form-data'
}
}
})
.then(res => {
//do something with reponse here
})
.catch(error => {
console.error(error)
})
}
...
Is there something wrong I am doing in the code or the request config is wrong?

UPPY: How to get json data returned from failed upload

I am using Uppy javascript library with xhr plugin to upload files.
How do I get the response data on a upload error? The below does not provide the json returned from my service. Documentation here
uppy.on('upload-error', function (file, error) {
console.log(error);
});
It can be passed as an option for XHR Upload plugin:
const XHRUpload = require('#uppy/xhr-upload')
uppy.use(XHRUpload, {
endpoint: 'http://my-website.org/upload',
getResponseError (responseText, response) {
return new Error(JSON.parse(responseText).message)
}
})
Same goes for getResponseData():
const XHRUpload = require('#uppy/xhr-upload')
uppy.use(XHRUpload, {
endpoint: 'http://my-website.org/upload',
getResponseData (responseText, response) {
return {
url: responseText.match(/<Location>(.*?)<\/Location>/)[1]
}
}
})
Here we are only showing the error message from the JSON response in the getResponseError() method but you can of course do pretty much anything with the response.
uppy takes three values in callback for upload-error event. So to get the status you can do something like this
uppy.on('upload-error', (file, error, response) => {
const { name } = file;
message.error(`Failed in uploading ${name}`);
if (response.status === 413) {
message.warning('Please check the file size');
}
});

Send a file from mobile to Node js server

I'm doing an application with react-native. Now I'm trying to send an image from the mobile to the server (Node Js). For this I'm using react-native-image-picker. And the problem is that when I send the image it save a file but it's empty not contain the photo. I think that the problem probably is that the server can't access to the path of the image because is in a different device. But I don't know how I can do it.
React-Native:
openImagePicker(){
const options = {
title: 'Select Avatar',
storageOptions: {
skipBackup: true,
path: 'images'
}
}
ImagePicker.showImagePicker(options, (imagen) =>{
if (imagen.didCancel) {
console.log('User cancelled image picker');
}
else if (imagen.error) {
console.log('ImagePicker Error: ', imagen.error);
}
else if (imagen.customButton) {
console.log('User tapped custom button: ', imagen.customButton);
}
else {
let formdata = new FormData();
formdata.append("file[name]", imagen.fileName);
formdata.append("file[path]", imagen.path);
formdata.append("file[type]", imagen.type);
fetch('http://X/user/photo/58e137dd5d45090d0b000006', {
method: 'PUT',
headers: {
'Content-Type': 'multipart/form-data'
},
body: formdata
})
.then(response => {
console.log("ok");
})
.catch(function(err) {
console.log(err);
})
}})}
Node Js:
addPhotoUser = function (req, res) {
User.findById(req.params.id, function(err, user) {
fs.readFile(req.body.file.path, function (err, data) {
var pwd = 'home/ubuntu/.../';
var newPath = pwd + req.body.file.name;
fs.writeFile(newPath, data, function (err) {
imageUrl: URL + req.body.file.name;
user.save(function(err) {
if(!err) {
console.log('Updated');
} else {
console.log('ERROR: ' + err);
}
res.send(user);
});
});
});
});
};
Yes, the problem is that the filepath is on the local device and not the server. You want to send the actual data returned to you by react-native-image-picker not the uri. It looks like that library encodes the data with base64 so you're going to want send that to your server, not the uri returned from the library because it won't be accessible on a remote server.
What this means is that you won't be reading any files on your server but instead just decoding a base64 string in the response body and writing that to your filesystem.
For the client side:
let formdata = new FormData();
formdata.append("file[name]", imagen.fileName);
formdata.append("file[data]", imagen.data); // this is base64 encoded!
formdata.append("file[type]", imagen.type);
fetch('http://X/user/photo/58e137dd5d45090d0b000006', {
method: 'PUT',
headers: {
'Content-Type': 'multipart/form-data'
},
body: formdata
})
On the server side atob to decode from base64 before writing to the filesystem:
let decoded = atob(req.body.data)
// now this is binary and can written to the filesystem
From there:
fs.writeFile(newPath, decoded, function (err) {
imageUrl: newPath;
user.save(function(err) {
if(!err) {
console.log('Updated');
} else {
console.log('ERROR: ' + err);
}
res.send(user);
});
});
Note, you don't need the filesystem write that's in your code because you're decoding the image that was sent as a b64 string in your request.
There also seems to be some oddities with how you're using that user object. You seem to be only passing a function that handles errors and not any actual data. I don't know what ORM you're using so it's hard to say how it should work. Maybe something like this?
user.save({imageUrl:uriReturnedByFsWrite}, (err, data)=>{...})
Good luck :)
Make an object then send that object to the server. The object will consist of name,path and type, like this:
var imageData = {name: 'image1', path: uri, type: 'image/jpeg'}
Above is a one way to send the image data. The other way is to convert it into BLOB so that server side programmer doesn't have to do this task on their end. You can make BLOB by use of react-native-fetch-blob.
One more way is to directly upload the images to the amazon server(s3) and send the link to the backend..
Function that returns base64 string:
var RNFetchBlob = require('react-native-fetch-blob').default;
getImageAttachment: function(uri_attachment, mimetype_attachment) {
return new Promise((RESOLVE, REJECT) => {
// Fetch attachment
RNFetchBlob.fetch('GET', config.apiRoot+'/app/'+uri_attachment)
.then((response) => {
let base64Str = response.data;
var imageBase64 = 'data:'+mimetype_attachment+';base64,'+base64Str;
// Return base64 image
RESOLVE(imageBase64)
})
}).catch((error) => {
// error handling
console.log("Error: ", error)
});
},
Cheers :)

Categories

Resources