I used the following js code to load a file with a Chinese filename into FormData and then upload it and found the filename garbled on the server side.
export async function uploadFile(file, url) {
let formData = new FormData()
formData.append('file', file.file)
file.status = 'loading'
let response = await fetch(url, { method: 'POST', body: formData})
file.status = response.ok
return response
}
How can I solve the filename garbling problem because the filenames in formdata seem to be ASCII encoded?
This question is used to share my solution. The code is below. I encoded the file name with encodeURIComponent and created a new File Object from it.
export async function uploadFile(file, url) {
let formData = new FormData()
const file_ = new File([file.file], encodeURIComponent(file.filename), {type: file.file.type});
formData.append('file', file_)
file.status = 'loading'
let response = await fetch(url, { method: 'POST', body: formData})
file.status = response.ok
return response
}
Afterwards I decode the filename with decodeURIComponent on the server side.
Related
I have a NestJs project in my controller I accept an Express.Multer.File but I need to send it to another server. How can I convert it to Blob for passing to FormData?
#action in my controller
#Post('/upload')
#UseInterceptors(FileInterceptor('avatar'))
async uploadUserProfileAvatar(#UploadedFile() file: Express.Multer.File){
console.log(file)
let blob = new Blob(file.mimetype)
let formData = new FormData()
//can't set because file is not Blob
formData.append('file',file)
let request$ = this.httpService.post('http://test_server/file',formData,{headers:{
'Content-Type':'multipart/form-data'
}}).pipe(
map(response => response.data)
)
request$.subscribe( response => {
console.log(response)
return response
})
}
I will be grateful for every answer!
EDIT:
Thanks again for your help! As a result, I managed to successfully send the code in this way:
#Post('/upload')
#UseInterceptors(FileInterceptor('avatar'))
async uploadUserProfileAvatar(#UploadedFile() file: Express.Multer.File){
const FormData = require('form-data');
const formData = new FormData();
formData.append('file', Buffer.from(file.buffer) , file.originalname);
let request$ = this.httpService.post('http://nginx_files/file',formData,{headers:formData.getHeaders()}).pipe(
map(response => response.data)
)
request$.subscribe( response => {
console.log(response)
return response
})
}
Use formdata package like this:
const FormData = require('form-data');
const formData = new FormData();
formData.append('file', fileContent, 'file_name.ext');
let request$ = this.httpService.post('http://test_server/file',
formData,
{ headers: formData.getHeaders() }
).pipe(
map(response => response.data)
)
#MiyRon
You get the Error "TypeError: source.on is not a function", because formData accepts only three types of elements. String, Buffer and Stream.
So you can fix it with:
formData.append('file', JSON.stringify(fileContent), 'file_name.ext');
Encountered same issue, but initial answer didn't work for me, had to add Content-Length header and contentType, so code looks something like this:
import * as FormData from 'form-data';
// ...
processFile(file: Express.Multer.File) {
const formData = new FormData();
formData.append('file', Buffer.from(file.buffer), {
filename: file.originalname,
contentType: file.mimetype,
});
return this.httpService
.post('http://test_server/file', formData, {
headers: {
...formData.getHeaders(),
'Content-Length': `${formData.getLengthSync()}`,
},
})
.pipe(map((response) => response.data));
}
Based on this comment
There are similar questions like this, this, this, and this, but none help.
In Node, the goal is to use the axios module to download an image from Twitter then upload this image as an image file as part of a form submission.
This code downloads the Twitter image, but the uploaded binary image is corrupted when it reaches the server (also our server).
The image must be uploaded in binary.
A form is required because other fields are also submitted with the binary image. These other form fields were removed to simplify the code sample.
const axios = require('axios');
const FormData = require('form-data');
let response = await axios.get('https://pbs.twimg.com/media/EyGoZkzVoAEpgp9.png', {
responseType: 'arraybuffer'
});
let imageBuffer = Buffer.from(response.data, 'binary');
let formData = new FormData();
formData.append('image', imageBuffer);
try {
let response = await axios({
method: 'post',
url: serverUrl,
data: formData,
});
// Do stuff with response.data
} catch (error) {
console.log(error)
}
You should pass the headers to the axios call using formData.getHeaders() to send a Content-Type header of multipart/form-data. Without it, a Content-Type header of application/x-www-form-urlencoded is sent. You could pass a responseType of stream to the axios call that downloads the image and add the stream to the form data.
You can also use axios.post to simplify the method call.
const url = 'https://pbs.twimg.com/media/EyGoZkzVoAEpgp9.png'
const { data: stream } = await axios.get(url, {
responseType: 'stream',
})
const formData = new FormData()
formData.append('image', stream)
try {
const { data } = await axios.post('http://httpbin.org/post', formData, {
headers: formData.getHeaders(),
})
console.log(data)
} catch (error) {
// handle error
}
You can use the fetch API to fetch the image as a blob object and append it to form data. Then simply upload it to its destination using Axios, ajax, or the fetch API:
const mediaUrl = "https://pbs.twimg.com/media/EyGoZkzVoAEpgp9.png"
fetch(mediaUrl)
.then((response) => response.blob())
.then((blob) => {
// you can also check the mime type before uploading to your server
if (!['image/jpeg', 'image/gif', 'image/png'].includes(blob?.type)) {
throw new Error('Invalid image');
}
// append to form data
const formData = new FormData();
formData.append('image', blob);
// upload file to server
uploadFile(formData);
})
.catch((error) => {
console.log('Invalid image')
});
I am using React-Dropzone npm to use a nicely styled drag and drop out of the box file uploader. I got stuck on the fact that React-Dropzone as of version 8.2.0 didn't include the paths to the file, e.g. shortened it with just the image name. They do however, provide a Blob Url. I can't figure out how to convert a Blob-URL to a Base64 string and then send that to Cloudinary.
Another way can be:
const url = 'blob:http://uri';
const blobToBase64 = (url) => {
return new Promise((resolve, _) => {
// do a request to the blob uri
const response = await fetch(url);
// response has a method called .blob() to get the blob file
const blob = await response.blob();
// instantiate a file reader
const fileReader = new FileReader();
// read the file
fileReader.readAsDataURL(blob);
fileReader.onloadend = function(){
resolve(fileReader.result); // Here is the base64 string
}
});
};
// now you can get the
blobToBase64(url)
.then(base64String => {
console.log(base64String) // i.e: data:image/jpeg;base64,/9j/4AAQSkZJ..
});
// or with await/async
const file = await blobToBase64(url);
console.log(file) // i.e: data:image/jpeg;base64,/9j/4AAQSkZJ..
I've figured it out:
After a few hours, and some nice people posting on StackOverflow I have pieced it together.
const getBlobData = (file) => {
axios({
method: "get",
url: file, // blob url eg. blob:http://127.0.0.1:8000/e89c5d87-a634-4540-974c-30dc476825cc
responseType: "blob",
}).then(function (response) {
var reader = new FileReader();
reader.readAsDataURL(response.data);
reader.onloadend = function () {
var base64data = reader.result;
const formData = new FormData();
formData.append("file", base64data);
formData.append("api_key", YOUR_API_KEY);
// replace this with your upload preset name
formData.append("upload_preset", YOUR_PRESET_NAME);//via cloudinary
axios({
method: "POST",
url: "https://api.cloudinary.com/v1_1/YOUR_CLOUD_NAME/upload",
data: formData,
})
.then((res) => {
const imageURL = res.data.url;
//YOU CAN SET_STATE HOWEVER YOU WOULD LIKE HERE.
})
.catch((err) => {
console.log(err);
});
};
});
};
I have a test that should read a image file and submit the image file to an api that accepts a multipart-formdata.
I am using the fetch api along with formdata class to set the image file. The formdata only accepts a blob. So in my test i must convert the the file i read in which is of type stream to a blob.
test("should submit front document", async () => {
const side = "front";
const stream = fs.createReadStream(process.cwd() + "/test/resources/" + "id/front.jpg");
const image = await streamToBlob(stream);
const front = await myLibrary.document(id, side, image);
expect(front.success).toBe(true);
});
I am attempting to use a library here to convert the stream to a blob https://www.npmjs.com/package/stream-to-blob. However the test is failing. If i attempt to console.log(image) i get the following Blob {}
Why is the blob empty {}?
async document(id, side, image) {
const url = this.API_URL + "/document"
let formData = new FormData();
formData.set("image", image, "front.jpg");
formData.set("side", side);
let headers = new Headers();
headers.set("Authorization", "Bearer " + this.API_KEY);
const request = {
method: "POST",
body: formData,
headers: headers,
};
try {
const response = await fetch(url, request);
const data = await response.json();
return data;
} catch (err) {
throw err;
}
}
A form I use to upload a book cover to Cloudinary has an input type='file' on it. However I'd like to allow no image upload too that is when the form submits there is no file provided to input type='file'. Cloudinary responds with Request failed with status code 400.
This is how I try to mock a file to upload it to Cloudinary inside an action.
export const addBook = (bookData, history) => (dispatch) => {
const cloudinaryUrl = 'https://api.cloudinary.com/v1_1/*****/upload';
const cloudinaryUploadPreset = '*****';
const formData = new FormData();
bookData.cover[0] && formData.append('file', bookData.cover[0]);
if (!bookData.cover[0]) {
const blob = new Blob([''], { type: 'image/png' });
blob['lastModifiedDate'] = '';
blob['name'] = 'mockImageFile';
blob['webkitRelativePath'] = '';
blob['size'] = 7654;
formData.append('file', blob);
/* const f = new File([''], 'filename', { type: 'image/jpeg' });
formData.append('file', f); */
}
formData.append('upload_preset', cloudinaryUploadPreset);
axios({
url: cloudinaryUrl,
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: formData
})
... ...
Still Cloudinary responds with Request failed with status code 400. How do I convince it to take a programmatically created 'file'?
The goal is to handle an upload with no file.
You need to convert blob data to a data URI which will be accepted by the file parameter to Cloudinary's upload method.
Please try below codebase to convert blob data to data URI. which you can append it to formdata.
const blob = new Blob([""],{ type: "image/png" });
blob["lastModifiedDate"] = "";
blob["name"] = "mockImageFile";
blob["webkitRelativePath"] = "";
blob["size"]=7654;
var reader = new FileReader();
var blob_base64data;
reader.readAsDataURL(blob);
reader.onloadend = function() { blob_base64data = reader.result;
};
formData.append("file", blob_base64data);
You can either perform a check for an empty file before uploading to Cloudinary and not upload to Cloudinary if there is no file or you can use a default image everytime there is no file uploaded instead of creating a blob.