I am struggling with a simple media (mp3/mp4) upload to a server using axios.
I have an angular application that creates a formData and send this formData to node server via :
return this.http.post(this.apiURL + '/uploadFile', formData);
My server method looks like this :
app.post('/api/uploadFile', upload.single('file'), (req, res) => {
inputFile = req.file;
let fd = new FormData();
fd.append('file',inputFile.buffer, inputFile.originalname);
axios.post(uploadFileURL , fd, { headers: { 'Content-Type': 'multipart/form-data' } })
.then((response) => {
console.log(response);
})
.catch((error) => {
console.error(error)
})
})
The inputFile contains the original files. The error I get now is that the request is not a multipart request...
I tried as well to define the formData differently :
formData = {
file: {
value: inputFile.buffer,
options: {
filename: inputFile.originalname,
contentType: inputFile.mimetype
}
}
};
Which brought me to a different error : 'Failed to parse multipart servlet request; nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found'
Am I doing something wrong ?
I am wondering if this could be link to the fact that I use const bodyParser = require('body-parser'); for some of my other requests.
Any help would be appreciated!
Thanks
EDIT :
Here is my need and what I've done so far :
I have a web application that allow users to upload media files.
I have to send those files to a server, but I can not use the browser to send the request directly.
I created a nodejs application to realize the proxy task of getting the files from the browser and sending it to my remote server.
Related
I found a website to store videos in cloud storage.
Here's the URL of it: Abyss
They have a very short document and it is written in "cURL" form, it's like this.
curl -F "file=#demo.mp4" up.hydrax.net/MY_API_KEY
So I tried using it with React + ExpressJS.
In my ReactJS, I have a button to submit my video like this:
const handleSubmit = async () => {
let formData = new FormData()
formData.append("file", selectVideo)
await axios.post(
`/film/add-episode/${_id}`,
{
formData,
},
{
headers: {
"content-type": "multipart/form-data",
},
}
)
}
I send that video/mp4 file to my Express POST route that uploads the video.
Here's my POST ExpressJS upload video route controller:
const filmController = {
addEpisode: async (req, res) => {
const { formData } = req.body
try {
console.log(formData) // What I get when passing mp4 from React to Express is an object FormData
//API URL to upload video
const response = await axios.post(
"http://up.hydrax.net/MY_API_KEY_HERE",
{
formData,
},
{
headers: {
"content-type": "multipart/form-data",
},
}
)
console.log(response)
res.json({ msg: "Success add episode" })
} catch (err) {
return res.status(500).json({ msg: err.message })
}
},
}
The reason I tried using axios at the back-end, too, was to get the slug that Abyss generated every time I success uploaded my video from Express to Abyss, I'm planning to save it to my MongoDB with more information, and I get rid of most of the other code like getting id, name, description, etc. for a cleaner look.
Here's how Abyss generates the slug, I need to get it with the post response at Express
But my problem right now is it is just pending my request forever, with no sight of returning the last message or anything, and even in the cloud storage, there was no file uploaded.
Does object FormData can't be read as an mp4 file? Because it seems to be stuck at the POST request I made at Express.
Ok, so i need to proxy file array upload from my express node app, to remote PHP Api.
Ideally i would use something close to Nginx proxy since it has same modules with node.
If not,i would emulate form resend.
Can you help find the best way for doing this?
So, i did not found execaly how to proxy request itself, as it would do nginx, but at least i figured out how to redirect request with all it's data to different source.
So, here we use express-fileupload to get our file data from req, and form-data to create a form and send data.
import app from '#app';
import { authMw as checkJwtAndAddToRequest } from '#middleware/Auth';
import { Router } from 'express';
import fileupload, { UploadedFile } from "express-fileupload";
import FormData from 'form-data';
//add this MW to add files to you'r req
app.use(checkJwtAndAddToRequest)
app.use(fileupload());
app.post('/upload', (req, res) => {
const uploadableFiles: UploadedFile[] | UploadedFile | undefined = req.files?.formFieldName;
if(!uploadableFiles) {
throw Error('Pls add files to upload them');
}
//Transorm single file to same form as an array of files
const files: UploadedFile[] = Array.isArray(uploadableFiles) ? uploadableFiles : Array<UploadedFile>(uploadableFiles);
//create form
const form = new FormData();
//add files
files.forEach(
file => form.append('files[]', file.data.toString(), file.name)
)
//Submit
form.submit({
protocol: 'http:',
host: process.env.API_URL,
path: '/api-path-for/file/upload',
method: 'POST',
headers: {
//Add auth token if needed
authorization: `Bearer ${String(req.body.jwtToken)}`
}
}, (err, response) => {
if(err) {
//handle error
res.status(503).send('File server is currently unavailable');
}
//return remote response to original client
response.pipe(res)
});
});
I'm trying to get a file from the user in my react application and then send that file to my back end and upload it to s3 bucket. I am successfully able to choose a file and it even shows up in the console. But when I send it to the back end, it just shows empty braces.
Here's the code to update the state:
handleInputChange = (event) => {
this.setState({
...this.state,
file: event.target.files[0]
})
}
This is the code to send the file
submit = () => {
console.log(this.state.file)
axios.post(process.env.REACT_APP_SERVER + '/uploadThumbnail', { file: this.state.file, status: true})
.then(response => {
console.log(response)
})
}
Now, I can see the file in console. But in backend, it shows
REQUEST BODY: {file:{}, status: false}
Anyone knows how to solve this issue?
It's not right to send the file as a json.
I guess you are using nodejs backend and maybe using multer for file upload?
To send a file object via api, you have to send it as a form data.
const data = new FormData();
data.append('file', this.state.file);
data.append('status', true);
axios.post(process.env.REACT_APP_SERVER + '/uploadThumbnail', data, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then(response => {
console.log(response)
})
I tried 400 combinations of syntaxes and headers, I can't figure out how to make a HTTP call from Angular to retrieve a file from my NodeJS server.
Found on Stackoverflow and tried, to no avail :
Download file from http post request - Angular 6
How download a file from HttpClient
Download a file from NodeJS Server using Express
How do I download a file with Angular2
It can't be a simple <a download> tag, or a public express.static() folder, because access to the file is restricted and I need to pass a JWT token along (in Node, I have an Express authentication middleware that will reject the request if no token is provided in the headers or if it is invalid).
The file is a GZIP : ./dumps/dump.gz and weighs 812 Kb.
I do manage to download the file, but whatever I try, it weighs 1.4 MB or 94 bytes (wrong size) and can't be opened (7zip can't open file downloads/dump.gz as archive).
What I have tried Angular-side (multiple attempts) :
import { saveAs } from 'file-saver';
let headers = new Headers({
"Authorization": "Bearer " + user.jwt, // I need this in the headers
"Content-Type" : "application/octet-stream", // Tried with and without, "application/gzip", "application/json", no difference
"responseType": "blob" as "json", // Tried with and without, "text", "json", no difference
"Access-Control-Expose-Headers" : "Content-Disposition" // Tried with and without, no difference
})
this.http
.get("/download/dump", { headers })
.toPromise()
.then(res => {
const blob = new Blob([res["_body"]] , { type: "application/octet-stream;"} ); // Error : body is not a blob or an array buffer
// const blob = new Blob([res["_body"]]); // Same result
// const blob = new Blob([res.blob()]); // Error : body is not a blob or an array buffer
saveAs(blob, "dump.gz"); // Saves a big corrupted file
// window.URL.createObjectURL(new Blob(blob, {type: 'blob'})); Saves a 94 byte corrupted file. Tried {type: 'gzip'}, same thing
})
.catch(err => console.error("download error = ", err))
What I have tried Node-side (multiple attempts) :
EDIT
Node has been innocented as I could retrieve the file directly from Chrome after disabling authentication. So, the back-end works and the issue is in Angular.
app.get( "/download/dump", authenticate, (req:Request, res:Response) => {
const file = path.resolve(__dirname, `./dumps/dump.gz`);
res
.set({ // Tried with and without headers, doesn't seem to do anything
"Content-Disposition" : "attachment", // Tried with and without
"filename" : "dump.gz", // Tried with and without
"filename*" : "dump.gz", // Tried with and without
"Content-Encoding" : "gzip", // Tried with and without
"Content-Type" : "application/gzip" // Tried with and without, "application/text", "application/json", no difference
})
.sendFile(file); // getting a big corrupted file
// .download(file); // Same result (big corrupted file)
})
Assuming that you are using the new HttpClient from angular (available since angular 4), this should work
front
import { saveAs } from 'file-saver';
import {HttpHeaders} from "#angular/common/http";
let headers = new HttpHeaders({
"Authorization": "Bearer " + user.jwt, // Auth header
//No other headers needed
});
this.http
.get("/download/dump", { headers, responseType: "blob" }) //set response Type properly (it is not part of headers)
.toPromise()
.then(blob => {
saveAs(blob, "dump.gz");
})
.catch(err => console.error("download error = ", err))
backend
app.get( "/download/dump", authenticate, (req:Request, res:Response) => {
const file = path.resolve(__dirname, `./dumps/dump.gz`);
//No need for special headers
res.download(file);
})
I need to send BLOB to the server in order to make an image on same.
I am using axios on reactJs client and sending data by using this code.
/**
* Returns PDF document.
*
*/
getPDF = (blob) =>
{
let formatData = new FormData();
formatData.append('data', blob);
return axios({
method: 'post',
url: 'http://172.18.0.2:8001/export/pdf',
headers: { 'content-type': 'multipart/form-data' },
data: {
blob: formatData
}
}).then(response => {
return {
status: response.status,
data: response.data
}
})
}
I tried to console.log this blob value on client and there is regular data.
But on server request body is empty.
/**
* Exports data to PDF format route.
*/
app.post('/export/pdf', function (request, response) {
console.log(request.body.blob);
response.send('ok');
});
If I remove headers still empty body when sending blob, but if I remove blob and send some string, a server receives data.
But when the blob is sent server has an empty body.
NodeJS natively does not handle multipart/form-data so you have to use external module eg :- multer
Code Example(Not Tested):
var upload = multer({ dest: __dirname + '/public/uploads/' });
var type = upload.single('upl');
/**
* Exports data to PDF format route.
*/
app.post('/export/pdf', type, function (request, response) {
// Get the blob file data
console.log(request.file);
response.send('ok');
});
you can read about multer here
I hope this will work for you.
Are you using body-parser?
body-parser doesn't handle multipart bodies, which is what FormData is submitted as.
Instead, use a module like multer
let multer = require('multer');
let upload = multer();
app.post('/export/pdf', upload.fields([]), (req, res) => {
let formData = req.body;
console.log('Data', formData);
res.status(200).send('ok');
});
I had 2 problems that I had to solve for this. 1 firebase functions has a bug that doesn't allow multer. 2 you may be getting a blob back from response.blob() and that doesn't seem to produce a properly formatted blob for firebase functions either.