Can't upload image using Angular's HttpClient - javascript

I am able to upload an image perfectly fine using the native fetch POST:
let formData = new FormData();
formData.append('file', event.target.files[0]);
console.log(event.target.files[0]);
fetch('http://localhost:8080/file/upload', {
method: 'POST',
headers:{
'Authorization': 'Bearer ' + JWT
},
body:formData
}).then(response => console.log(response));
However, when I try this using Angular's HttpClient, the request fails since the 'Content-Type': 'multipart/form-data' is not added.
My Angular code:
The file where I'm calling the service:
this.fileSaverService.uploadImage(event.target.files[0]).subscribe(
(data)=>{
console.log(data);
},
(error) => {
console.log(error);
}
);
The fileSaverService:
uploadImage(fileToUpload) {
const endpoint = 'http://localhost:8080/file/upload';
const formData: FormData = new FormData();
formData.append('file', fileToUpload);
return this.api
.post(endpoint, formData, { headers: {'Content-Type': 'multipart/form-data'} });
}
The this.api:
post(endpoint: string, body: any, reqOpts: any = {}) {
reqOpts.headers = this.handleHeaders(reqOpts.headers);
return this.http.post(endpoint, JSON.stringify(body), reqOpts);
}
If I add the header manually when using HttpClient, I get this header without the boundary:
However the native fetch adds this header automatically with the boundary if I don't specify a header and this works perfectly:
What are my options here?

There are two problems with the code in your question:
By setting Content-Type to multipart/form-data yourself, the serialisation of your FormData will not result in the correct header with boundaries being set behind the scenes.
In your post function, you have JSON.stringify(body), which is converting your FormData object into a JSON string. After doing this, you're simply attempting to POST a string rather than a complete FormData object.

Related

ECONNRESET and CGI parser error when trying to upload file using axios post [duplicate]

I have an API endpoint that lets the client post their csv to our server then post it to someone else server. I have done our server part which save uploaded file to our server, but I can't get the other part done. I keep getting error { message: 'File not found', code: 400 } which may mean the file never reach the server. I'm using axios as an agent, does anyone know how to get this done? Thanks.
// file = uploaded file
const form_data = new FormData();
form_data.append("file", fs.createReadStream(file.path));
const request_config = {
method: "post",
url: url,
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios(request_config);
Update
As axios doc states as below and the API I'm trying to call requires a file
// data is the data to be sent as the request body
// Only applicable for request methods 'PUT', 'POST', and 'PATCH'
// When no transformRequest is set, must be of one of the following types:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - Browser only: FormData, File, Blob
// - Node only: Stream, Buffer
Is there any way to make axios send a file as a whole? Thanks.
The 2 oldest answers did not work for me. This, however, did the trick:
const FormData = require('form-data'); // npm install --save form-data
const form = new FormData();
form.append('file', fs.createReadStream(file.path));
const request_config = {
headers: {
'Authorization': `Bearer ${access_token}`,
...form.getHeaders()
}
};
return axios.post(url, form, request_config);
form.getHeaders() returns an Object with the content-type as well as the boundary.
For example:
{ "content-type": "multipart/form-data; boundary=-------------------0123456789" }
I'm thinking the createReadStream is your issue because its async. try this.
Since createReadStream extends the event emitter, we can "listen" for when it finishes/ends.
var newFile = fs.createReadStream(file.path);
// personally I'd function out the inner body here and just call
// to the function and pass in the newFile
newFile.on('end', function() {
const form_data = new FormData();
form_data.append("file", newFile, "filename.ext");
const request_config = {
method: "post",
url: url,
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios(request_config);
});
This is what you really need:
const form_data = new FormData();
form_data.append("file", fs.createReadStream(file.path));
const request_config = {
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios
.post(url, form_data, request_config);
In my case, fs.createReadStream(file.path) did not work.
I had to use buffer instead.
const form = new FormData();
form.append('file', fs.readFileSync(filePath), fileName);
const config = {
headers: {
Authorization: `Bearer ${auth.access_token}`,
...form.getHeaders(),
},
};
axios.post(api, form.getBuffer(), config);
I have made an interceptor you can connect to axios to handle this case in node: axios-form-data. Any feedback would be welcome.
npm i axios-form-data
example:
import axiosFormData from 'axios-form-data';
import axios from 'axios';
// connect axiosFormData interceptor to axios
axios.interceptors.request.use(axiosFormData);
// send request with a file in it, it automatically becomes form-data
const response = await axios.request({
method: 'POST',
url: 'http://httpbin.org/post',
data: {
nonfile: 'Non-file value',
// if there is at least one streamable value, the interceptor wraps the data into FormData
file: createReadStream('somefile'),
},
});
// response should show "files" with file content, "form" with other values
// and multipart/form-data with random boundary as request header
console.log(response.data);
I had a same issue, I had a "pdf-creator-service" for generate PDF document from html.
I use mustache template engine for create HTML document - https://www.npmjs.com/package/mustache
Mustache.render function returns html as a string what do I need to do to pass it to the pdf-generator-service ? So lets see my suggestion bellow
//...
async function getPdfDoc(props: {foo: string, bar: string}): Promise<Buffer> {
const temlateFile = readFileSync(joinPath(process.cwd(), 'file.html'))
mustache.render(temlateFile, props)
const readableStream = this.getReadableStreamFromString(htmlString)
const formData = new FormData() // from 'form-data'
formData.append('file', options.file, { filename: options.fileName })
const formHeaders = formData.getHeaders()
return await axios.send<Buffer>(
{
method: 'POST',
url: 'https://pdf-generator-service-url/pdf',
data: formData,
headers: {
...formHeaders,
},
responseType: 'arraybuffer', // ! important
},
)
}
getReadableStreamFromString(str: string): Readable {
const bufferHtmlString = Buffer.from(str)
const readableStream = new Readable() // from 'stream'
readableStream._read = () => null // workaround error
readableStream.push(bufferHtmlString)
readableStream.push(null) // mark end of stream
return readableStream
}
For anyone who wants to upload files from their local filesystem (actually from anywhere with the right streams architecture) with axios and doesn't want to use any external packages (like form-data).
Just create a readable stream and plug it right into axios request function like so:
await axios.put(
url,
fs.createReadStream(path_to_file)
)
Axios accepts data argument of type Stream in node context.
Works fine for me at least in Node v.16.13.1 and with axios v.0.27.2

how to add file with formData in axios request using node.js [duplicate]

I have an API endpoint that lets the client post their csv to our server then post it to someone else server. I have done our server part which save uploaded file to our server, but I can't get the other part done. I keep getting error { message: 'File not found', code: 400 } which may mean the file never reach the server. I'm using axios as an agent, does anyone know how to get this done? Thanks.
// file = uploaded file
const form_data = new FormData();
form_data.append("file", fs.createReadStream(file.path));
const request_config = {
method: "post",
url: url,
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios(request_config);
Update
As axios doc states as below and the API I'm trying to call requires a file
// data is the data to be sent as the request body
// Only applicable for request methods 'PUT', 'POST', and 'PATCH'
// When no transformRequest is set, must be of one of the following types:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - Browser only: FormData, File, Blob
// - Node only: Stream, Buffer
Is there any way to make axios send a file as a whole? Thanks.
The 2 oldest answers did not work for me. This, however, did the trick:
const FormData = require('form-data'); // npm install --save form-data
const form = new FormData();
form.append('file', fs.createReadStream(file.path));
const request_config = {
headers: {
'Authorization': `Bearer ${access_token}`,
...form.getHeaders()
}
};
return axios.post(url, form, request_config);
form.getHeaders() returns an Object with the content-type as well as the boundary.
For example:
{ "content-type": "multipart/form-data; boundary=-------------------0123456789" }
I'm thinking the createReadStream is your issue because its async. try this.
Since createReadStream extends the event emitter, we can "listen" for when it finishes/ends.
var newFile = fs.createReadStream(file.path);
// personally I'd function out the inner body here and just call
// to the function and pass in the newFile
newFile.on('end', function() {
const form_data = new FormData();
form_data.append("file", newFile, "filename.ext");
const request_config = {
method: "post",
url: url,
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios(request_config);
});
This is what you really need:
const form_data = new FormData();
form_data.append("file", fs.createReadStream(file.path));
const request_config = {
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "multipart/form-data"
},
data: form_data
};
return axios
.post(url, form_data, request_config);
In my case, fs.createReadStream(file.path) did not work.
I had to use buffer instead.
const form = new FormData();
form.append('file', fs.readFileSync(filePath), fileName);
const config = {
headers: {
Authorization: `Bearer ${auth.access_token}`,
...form.getHeaders(),
},
};
axios.post(api, form.getBuffer(), config);
I have made an interceptor you can connect to axios to handle this case in node: axios-form-data. Any feedback would be welcome.
npm i axios-form-data
example:
import axiosFormData from 'axios-form-data';
import axios from 'axios';
// connect axiosFormData interceptor to axios
axios.interceptors.request.use(axiosFormData);
// send request with a file in it, it automatically becomes form-data
const response = await axios.request({
method: 'POST',
url: 'http://httpbin.org/post',
data: {
nonfile: 'Non-file value',
// if there is at least one streamable value, the interceptor wraps the data into FormData
file: createReadStream('somefile'),
},
});
// response should show "files" with file content, "form" with other values
// and multipart/form-data with random boundary as request header
console.log(response.data);
I had a same issue, I had a "pdf-creator-service" for generate PDF document from html.
I use mustache template engine for create HTML document - https://www.npmjs.com/package/mustache
Mustache.render function returns html as a string what do I need to do to pass it to the pdf-generator-service ? So lets see my suggestion bellow
//...
async function getPdfDoc(props: {foo: string, bar: string}): Promise<Buffer> {
const temlateFile = readFileSync(joinPath(process.cwd(), 'file.html'))
mustache.render(temlateFile, props)
const readableStream = this.getReadableStreamFromString(htmlString)
const formData = new FormData() // from 'form-data'
formData.append('file', options.file, { filename: options.fileName })
const formHeaders = formData.getHeaders()
return await axios.send<Buffer>(
{
method: 'POST',
url: 'https://pdf-generator-service-url/pdf',
data: formData,
headers: {
...formHeaders,
},
responseType: 'arraybuffer', // ! important
},
)
}
getReadableStreamFromString(str: string): Readable {
const bufferHtmlString = Buffer.from(str)
const readableStream = new Readable() // from 'stream'
readableStream._read = () => null // workaround error
readableStream.push(bufferHtmlString)
readableStream.push(null) // mark end of stream
return readableStream
}
For anyone who wants to upload files from their local filesystem (actually from anywhere with the right streams architecture) with axios and doesn't want to use any external packages (like form-data).
Just create a readable stream and plug it right into axios request function like so:
await axios.put(
url,
fs.createReadStream(path_to_file)
)
Axios accepts data argument of type Stream in node context.
Works fine for me at least in Node v.16.13.1 and with axios v.0.27.2

How to send data correct axios Error: Multipart: Boundary not found

I don't know why I receive on server [Error: Multipart: Boundary not found]
and bundle.js:37628 POST http://localhost:8800/exporttocsv 500 (Internal Server Error)
When I make post through
<form action="/exporttocsv" method="POST" encType="multipart/form-data">
post works correctly, but through axios doesn't work.
Please help me fix the mistake
this my code
/--client
import axios from 'axios'
var formData = new FormData()
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
export const ipmortToCSV = (file) => dispatch => {
formData.append('file',file)
console.log(formData.getAll('data'))
axios.post('/exporttocsv', {
"UploadCommand": formData
},config)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
//--server
const router = require('express').Router()
var csv = require('csv-express')
const controllers = require('../../controllers/exporttocsv')
var multer = require('multer')
var upload = multer({dest : 'exporttocsv/'})
router.get('/', (req, res) => {
controllers.exportToCsv(req,res)
})
router.post('/',upload.single('file'),(req,res) => {
//controllers.importToCsv(req,res)
})
module.exports = router
You can do this ...
Instantiate a new FormData instance.
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
let fd = new FormData();
fd.append('file',files[0])
return axios.post("http://localhost:5000/upload", fd, config)
Usingconcat and concat-stream
const concat = require("concat-stream")
const fd = new FormData()
fd.append("hello", "world")
fd.append("file", fs.createReadStream(file))
fd.pipe(concat(data => {
axios.post("/hello", data, {
headers: fd.getHeaders()
})
}))
Using promise
const promise = new Promise((resolve) => {
const fd = new FormData();
fd.append("hello", "world");
fd.append("file", fs.createReadStream(binaryFile));
fd.pipe(concat({ encoding: 'buffer' }, data => resolve({ data, headers: fd.getHeaders() })));
});
promise.then(({ data, headers }) => axios.post('/hello', data, { headers }));
I hope I've been useful! :)
References:
github.com - Can't get a .post with Content-Type...
github.com - Better solution using axios, form-data, fs
https://stackoverflow.com/a/47630754/3332734
I was struggling with this issue of multipart boundary not found with fetch api calling to a nestjs server. What I tried was to remove the
'Content-Type': 'multipart/form-data',
headers so that Fetch api automatically set the headers and it worked. Try it out
By default axios do not attach boundary to content type header. You have to do it manually:
axios.post(`${this.baseUrl}/${path}`, formData, {
headers: {
'Content-Type': `multipart/form-data; boundary=${formData.getBoundary()}`,
},
})
It is especially important if you talking to spring server.
In other case you will see exception:
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
I was getting this problem with Axios via JavaScript because the content-type header was multipart-form-data but the boundary was missing.
Based on my research, a good way to handle it is to allow Axios to auto-detect the content type and set the headers correctly itself.
Here is an idea for how to accomplish this:
const formDataWithFiles = hasFiles ? new FormData() : undefined;
if (formDataWithFiles) {
// axios will automatically set the content-type to multipart/form-data if the
// data param is a FormData object
// otherwise, it will use application/json
// (study the Dev Tools > Network tab > XHR tab headers)
Object.keys(modifiedFields)
.forEach(field => formDataWithFiles.append(field, modifiedFields[field]));
}
const { data } = await axios({
method,
url: actionUrl,
data: hasFiles ? formDataWithFiles : modifiedFields,
headers: {
...axios.defaults.headers,
...headers,
},
});
return data;
The above code is in a generic handleSubmit function that can be called from anywhere in the client-side.
Here is the function signature:
const { data } = await this.submitForm({
actionUrl: this.actionUrl,
method: this.method,
modifiedFields: {
...this.modifiedUser,
},
hasFiles: true,
});
In the above code, there are two use cases. The first is the default case, where a normal payload is sent via a flat object. The second is the case when the form has files and you want multipart/form-data. In this case, we use the FormData Object as a vessel to instruct Axios to auto-detect the necessary headers and set the correct boundary.
If you do not specify the headers correctly, it is possible to receive an empty $request->all() Array in Laravel, or perhaps any server such as node.js.
The short answer to my answer is to use the FormData Object because it contains more information than a plain-old-JavaScript-object. With it, you can also access:
const formData = new FormData();
console.log('boundary:', formData._boundary);
As my annotation above hints towards, use the Dev Tools > Network tab > XHR tab to examine your request headers and make sure you have content-type application/json or application/x-www-form-urlencoded for regular form submits and multipart/form-data' if you are uploading a file.
For me the main reason was what the OP did; sending the data argument of axios.post as an object ({ key: formDataObj}) instead of just formDataObj directly as the arg.
For me add the following code to fixes it.
axios.post('/xxx/Upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
transformRequest: (data) => {
return data
},
})
Okay, I would like to share my solution as I struggled with this problem for almost a day. The issue was caused by an incorrect package version. Around 27.0.0, some changes were made to the form data sending, which resulted in issues with the boundaries. I'm not sure what version you folks are using, but it might be worth checking if this is the cause of your problem.
https://github.com/axios/axios/issues/4631

Cannot get images to upload to wp/v2/media with React Native

I've been all over here and https://github.com/WP-API/WP-API/ and https://wordpress.org/ and cannot find anyone talking about this. I can get it working using postman and selecting the file, however i cannot get it work submitting it as either "multipart/form" or "image/png".
I also cannot find anything detailing exactly how the request should look. I can create attachments but they're empty images of the correct size using base64 data as the post body. I feel like I'm missing something simple but cannot figure out what it is. I'm using postman to abstract out other distractions, my headers are:
POST /wp-json/wp/v2/media
Content-Type: image/png
Content-Disposition: attachment;filename=image_1.png
Cache-Control: no-cache
Authorization: Bearer {JWT_Auth_token}
body is just
{
data:image/png;base64,{base64_string_here}
}
I get a returned response like it created an attachment, but when I check in the wp admin it's an blank file, that appears to be the correct kb size but doesn't display. What am I missing???
Finally figured it out! With the help of this WP Trac issue https://core.trac.wordpress.org/ticket/41774.
So my request looks like this now:
async function uploadImageAsync(urlbase, uri, base64, token) {
let apiUrl = urlbase + '/wp-json/wp/v2/media';
let formData = new FormData();
//dynamically get file type
let uriParts = uri.split('.');
let fileType = uriParts[uriParts.length - 1];
//generate some random number for the filename
var randNumber1 = Math.floor(Math.random() * 100);
var randNumber2 = Math.floor(Math.random() * 100);
formData.append('file', {
base64,
name: `photo-${randNumber1}-${randNumber2}.${fileType}`,
type: `image/${fileType}`,
});
let options = {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
'Authorization' : 'Bearer ' + token,
'Content-Type': 'multipart/form-data',
'Cache-Control' : 'no-cache',
},
};
console.log('header options: ',options);
console.log('form-data options: ',formData);
return fetch(apiUrl, options);
}
When base64 comes in it's formatted simply as base64: {base64-string}. Not data:image/type, since that is specified in the form data. The other thing that's key here is setting the key of form to 'file'.
The original answer did not work for me, so I changed "uri: base64,". now it's working fine
formData.append('file', {
uri: base64,
name: `photo-${randNumber1}-${randNumber2}.${fileType}`,
type: `image/${fileType}`,
});
I wanted to put my solution somewhere in case other people find it useful. I am using react-native and redux, this solution is an action within the redux framework.
export const uploadMedia = image => {
return async function(dispatch, getState) {
const { token } = getState();
const uriParts = image.uri.split('.');
const fileType = uriParts[uriParts.length - 1];
const bodyFormData = new FormData();
bodyFormData.append('file', {
base64: image.base64,
name: `photo-qa.${fileType}`,
type: `image/${fileType}`
});
try {
const request = await fetch(
'http://stagingsite.test/wp-json/wp/v2/media',
{
method: 'POST',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
'Content-Type': 'multipart/form-data; boundary=__boundrytext__'
},
body: bodyFormData
}
);
const response = await request.json();
console.log({
text: 'uploadMedia', response, request
});
} catch (error) {
return ErrorHandler(error);
}
};
};

why I use fetch to upload img in react don't have fileName?

I use fetch to upload images in react,my code is like below:
let formData = new FormData();
let file = {uri: imgdata, type: 'multipart/form-data', name: '2_resources.jpg'};
formData.append("name", "name");
formData.append("mobile", "18381307123");
formData.append("content", "123654");
formData.append("resources", file,"2_resources.jpg");//mind this line
fetch(Config.report,
{
mode: 'cors',
method: "POST",
body: formData
})
.then(res => res.json())
.then((data) => {
console.log(data)
}
).catch((err) => {
console.log(err)
}
);
but,run it I get the request :
I have look for FormData API document on https://developer.mozilla.org/en-US/docs/Web/API/FormData
it have write below:
and the fetch used is :"isomorphic-fetch": "^2.2.1"
what should I do to use fetch upload images? thanks.
You are adding the file to the FormData object incorrectly. isomorphic-fetch wraps github's fetch polyfill. Looking at their docs shows that it should be:
handleFileUpload(event) {
var formData = new FormData();
formData.append('fileName', event.target.files[0]);
fetch('http://your-domain/upload',
{
mode: 'cors',
method: 'POST',
body: formData
}).then(function (response) {
console.log('response: ', response);
});
}
The field name on the server will be whatever you use as the key when appending formData. In the above example it will be 'fileName'. I also don't think you need to set the content-type and file name yourself. It should get set automatically.
In the above example, 'event' is the event fired from the input tag when the user upload the file:
<input type="file" onChange={this.handleFileUpload}/>

Categories

Resources