AWS S3 update image using pre-signed URL (Axios-PUT Request) - javascript

I'm trying to update a local JPG image file into an S3 bucket using the REST PUT request and Axios.
I managed to send the PUT request and to get a positive answer from AWS S3 Service but what it's been upload is not a JPG file but a JSON file.
This is the code that I'm using:
//Create the FormData
var data = new FormData();
data.append('file', fs.createReadStream(image_path));
//Send the file to File-system
console.log("Sending file to S3...");
const axiosResponse = await axios.put(image_signed_url, {
data: data,
headers: { 'Content-Type': 'multipart/form-data' }
}).catch(function(error) {
console.log(JSON.stringify(error));
return null;
});
I have already try to change the headers to {'Content-Type': 'application/octet-stream' } but I obtained the same result.

It did not manage to make AXIOS work in order to upload an image.
The node-fetch module did it sending the image as a binary and specifying the "Content-type".
If I try to the same using AXIOS it the image was always packet into a form-data and the result was JSON file uploaded into the S3 bucket instead of the image.
//Send the file to File-system
console.log("Sending file to S3...");
const resp = await fetch(image_signed_url, {
method: 'PUT',
body: fs.readFileSync(image_path),
headers: {
'Content-Type': 'image/jpeg',
},
}).catch( err => {
console.log(err);
return null;
});

Related

Send a local file via Axios for debug purposes

I am testing my application, and I want to send a local mp3 file that I have stored in my storage folder.
How can I send this via Axios? Should I convert it to a Blob/File, and if so, how?
Thanks in advance.
This code below is how I tried it, but it didn't work.
const formData = new FormData();
const file = new File('storage/app/audio/debug.mp3', 'test.mp3', {
type: Blob,
lastModified: Date.now()
});
formData.append('data', file);
formData.append('id', urlId[2]);
return Axios.post("http://voice-app.test/api/v1/file", formData, {
headers: {
'Authorization': `Bearer ${localStorage.getItem("Bearer")}`,
}
}).catch((e) => {
console.error(e);
})

Push File Download on Ajax POST Nodejs Express

On Node Server i have this code . Its basically sending the browser POST data to api server and recieves a file as chunk data and the same data is send back to browser via pipe response . But the issue is the api reponse is correct and i can write the file using nodejs locally but it doesnt push download file in browser
router.post('/MyURLOnNODE', function (req, res) {
var MyJsonData = { some Data };
res.writeHead(200, {'Content-disposition': 'attachment; filename=fileArchive.tgz','Content-Type': 'application/force-download'});
try {
request({
url: 'SomeAPI Url', //URL to hit
method: 'POST',
json: MyJsonData
}).pipe(res);
} catch(e) {
console.log("Pipe error",e);
}
console.log("File Streaming Called ",MyJsonData)
}
);
Client Side Code ...This was an attempt to create a blob and use it on urlObject. but the downloaded file is corrupted when i checked in hex.
$http({
method: 'POST',
url: 'MyURLOnNODE',
data: PostData, //forms user object
headers: {
'Content-Type': 'application/json'
}
})
.then(function (data) {
var response=data.data;
var id = Flash.create('success', "Folder Archieved", 5000);
var a = document.getElementById('arch');
a.href = URL.createObjectURL(new Blob([response]));
a.download = "FolderMe.tgz";
a.type='application/octet-stream '
a.click();
}
So is there any solution to this ? either on NodeJs Side or On browser
Update : https://stackoverflow.com/a/7969061/7078299
This thread says its hard to convert an ajax request to download file. So i need to work on client on using urlobject. But blob isnt working with stream data. How to solve it
You can use a node module called file-saver and use saveAs method provided by it and download the file on the client side.
First npm install file-saver;
You can use it as
import {saveAs} from 'file-saver';
and inside your then block you need to add this line
saveAs(new Blob([response]), <your file name>)
It will auto-download your file.
i fixed the issue by adding a reponseType on Client Side Code. Now the blob and ObjectUrl works correctly
$http({
method: 'POST',
url: 'MyUrl',
data: PostData, //forms user object
headers: {
'Content-Type': 'application/json'
},
responseType: 'arraybuffer'
})
.then(function (response) {
console.log(response);
var headers = response.headers();
var blob = new Blob([response.data],{type:headers['content-type']});
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "Archive.tgz";
link.click();
});

How to get text value from multipart/form-data

I'm building a React app, and one of the components is creating a FormData object with two fields - one is a file and the other is a string. The FormData is being sent as a PUT method to an express route, and its type is multipart/form-data because there's a file to upload. There I need to get the string valie from the form (called path), then use multer/multer-s3 to upload the file to AWS S3 to the specified path.
I'm stuck on how to do this. I wasn't able to find an answer on how to retrieve a text field from a multipart request. I did see How to Post "multipart/form-data" Form and get the Text field values from the Node.js server? and several similar suggestions, however the suggested answer did not work for me.
On the body-parser website, it says "This does not handle multipart bodies" - so that also means that bodyParser.urlencoded wouldn't help in my case.
React component:
const path = `/somepathhere/`;
const formData = new FormData();
formData.append('file', file);
formData.append('path', path)
// call API handler
apiActions.js:
const response = await fetch(apiUrl, {
method: "PUT",
body: formData
})
server.js:
// other code taken out for brevity
// middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// other code taken out for brevity
app.put('/api/uploadToS3', (req, res) => {
const path = '';
const storage = multerS3({
s3: s3,
bucket: process.env.AWS_S3_BUCKET,
contentType: multerS3.AUTO_CONTENT_TYPE,
cacheControl: 'max-age=31536000',
metadata: function (req, file, cb) {
cb(null, {fieldName: file.fieldname});
},
key: function (req, file, cb) {
cb(null, path + file.originalname)
}
})
const upload = multer({
storage: storage
}).any();
upload(req,res,function(err) {
if(err) {
console.log(err);
return res.end("Error uploading file.");
} else {
res.end("File has been uploaded");
}
});
});
If there is a better way to handle this, I would also like to know. The end goal is that the file should be uploaded to an S3 path dynamically determined in the React app. I tried sending an object with formData and path like such:
const response = await fetch(apiUrl, {
method: "PUT",
body: {formData: formData, path: path}
})
But that gave the error: [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client. Trying to add headers to the fetch request likewise gave various errors.

Upload Excel File from React to C# ASP.NET Core backend

I am trying to upload a file from a react front end to a C# backend. I am using drop zone to get the file and then I call an api helper to post the file but I am getting different errors when I try different things. I am unsure exactly what the headers should be and exactly what I should send but I get two distinct errors. If I do not set the content-type I get 415 (Unsupported Media Type) error. If I do specify content type as multipart/form-data I get a 500 internal server error. I get the same error when the content-type is application/json. The url is being past in and I am certain it is correct. I am unsure if the file should be appended as file[0][0] as I have done or as file[0] as it is an array but I believe it should be the first. Any suggestions welcome :) Here is my api post helper code:
export const uploadAdminFile = (file, path, method = 'POST', resource =
config.defaultResource) => {
const url = createUrl(resource, path);
const data = new FormData();
data.append('file', file[0][0]);
data.append('filename', file[0][0].name);
const request = accessToken =>
fetch(
url,
{
method,
mode: 'cors',
withCredentials: true,
processData: false,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json', //'multipart/form-data',
Authorization: `Bearer ${accessToken}`,
},
body: data,
})
.then(res => res.json())
.then(success => console.log('API HELPER: file upload success: ', success)
.catch(err => console.log('API HELPER: error during file upload: ', err)));
return sendRequest(request, resource);
};
Thanks for the help and suggestions, it turned out to be a backend issue but even still I learned a lot in the process. I will post my working code here in case anyone comes across this and finds it useful.
export const uploadAdminFile = (file, path, resource=config.defaultResource) => {
const url = createUrl(resource, path);
const formData = new FormData();
formData.append('file', file[0][0]);
formData.append('filename', file[0][0].name);
const request = accessToken =>
fetch(url,
{
method: 'POST',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: formData,
});
return sendRequest(request, resource);
};
As mentioned, the file name does not need to be sent separately and count be omitted. I am indexing the file this way because I get it from dropzone as an array and I only want a single file (the first one in the array). I hope this helps someone else out and here is a link to the mdn fetch docs (good information) and a good article on using fetch and formData.

Angular 2+ HTTP POST and GDrive API. Resumable file upload with name

I am trying to upload files to Google Drive in Angular 2. So far I am able to upload files, but without title and they are "Untitled"
Here is code to do that:
gDriveUploader(file): Promise<any> {
let authToken = tokenHere;
const url = `https://www.googleapis.com/upload/drive/v2/files/`
let formData:FormData = new FormData();
formData.append('title', file, file.name);
let headers = new Headers({
'Authorization': 'Bearer ' + authToken
});
headers.append('Accept', file.type);
let options = new RequestOptions ({
headers: headers,
});
console.log('OPTIONS: ', options)
return this.http.post(`${url}`, formData, options)
.toPromise()
.then(response => response.json())
.catch(e=>console.log(e));
}
I know, that in order to send metadata with file, I have to add this metadata to Request body and use at multipart or resumable upload types. But here I faced issue after issue and just can't make it properly.
I completely messed up. Here is on of my approaches with resumable upload type:
gDriveUploader(file): Promise<any> {
let authToken = token;
const url = `https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable`
console.log('FILE TO UPLOAD: ', file)
let formData:FormData = new FormData();
formData.append('name', file, file.name);
let headers = new Headers({
'Authorization': 'Bearer ' + authToken,
'Content-Type': 'application/json; charset=UTF-8', //if remove "Bad Content" Error
//'Content-Length': file.size, not sure if this one right?
});
let options = new RequestOptions ({
headers: headers,
});
return this.http.post(`${url}`, formData, options)
.toPromise()
.then(response => response.json())
.catch(e=>console.log(e));
}
that's not only two of my approaches...
According to Drive API for resumable upload:
POST https://www.googleapis.com/drive/v3/files?uploadType=resumable
HTTP/1.1
Authorization: Bearer [YOUR_AUTH_TOKEN]
Content-Length: 38
Content-Type: application/json; charset=UTF-8
X-Upload-Content-Type: image/jpeg
X-Upload-Content-Length: 2000000
What is Content-Length: 38? it's file length and I can just use file.size?
With multipart I can't figure out how to add those boundary separator in the request.
I saw some Q and A, that multipart were not supported by Angular, but that was 1-2 year ago. What about now?
Can I somehow use resumable upload to GDrive with additional file metadata using standard Angular features?
So. A bit more research on how API works. I came up with the following solution for resumable file upload. Main Idea, that first time I have to make a request and "set metadata" for my file and get response with the link, where to upload the file. And this link came as one of the response header called location.
Here is fully working code. Just pass File object to the first function.
I just quickly made 2 functions for this. First one will set metadata (just name) and call second function to upload just binary data.
gDriveUploader(file): Promise<any> {
let authToken = token
const url = `https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable`
let headers = new Headers({
'Authorization': 'Bearer ' + authToken,
'Content-Type': 'application/json; charset=UTF-8',
});
let options = new RequestOptions ({
headers: headers,
});
return this.http.post(`${url}`, {name: file.fullName}, options) //just set the name
.toPromise()
.then(response => this.gDriveUploadFile(file, response.headers.get('location'))) //call second function to upload `file` to proper URI from response
.then(response => {
let id = response.json().id //parse id of uploaded file
let resp = {fileName: file.fullName, fileType: file.fileType, fileSize: file.size, fileId: id} //create an object with file file properties, if you need that
return resp // return object back to function that called this service
})
.catch(e=>console.log(e));
}
Second function to upload data:
gDriveUploadFile(file, url): Promise<any> { //file and url we got from first func
let authToken = token
let headers = new Headers({
'Authorization': 'Bearer ' + authToken,
'Content-Type': 'application/json; charset=UTF-8',
'X-Upload-Content-Type': file.type
});
let options = new RequestOptions ({
headers: headers,
});
return this.http.post(`${url}`, file, options) //call proper resumable upload endpoint and pass just file as body
.toPromise()
}
Maybe solution not ideal, so far I do not handle errors here and do not use resumable features like uploading by chunks, just upload file at once. But hopefully if someone else stuck with GDrive uploading can get an idea.

Categories

Resources