Upload Image from triggerio to express.js fails - express

I'm trying to post a file from a mobile triggerio app to a nodejs service. The request hits the post method, but the fails because the form object is null in the request obj.
(TypeError: Cannot call method 'complete' of undefined)
I've adapted the code from the accepted answer in this post : Uploading images using Node.js, Express, and Mongoose
This is my current nodejs code:
var express = require('express')
, form = require('connect-form');
var app = express.createServer(
form({ keepExtensions: true })
);
app.post('/fileupload', function(req, res, next){
//req. form is nulL
req.form.complete(function(err, fields, files){
if (err) {
next(err);
} else {
console.log('\nuploaded %s to %s'
, files.image.filename
, files.image.path);
res.redirect('back');
}
});
req.form.on('progress', function(bytesReceived, bytesExpected){
var percent = (bytesReceived / bytesExpected * 100) | 0;
process.stdout.write('Uploading: %' + percent + '\r');
});
});
app.listen(process.env.PORT);
console.log("express started");
And this is my upload method in triggerio:
function uploadFile (file){
forge.request.ajax({
url: 'http://resttrigger.aykarsi.c9.io/fileupload',
type: 'POST',
files: [file],
fileUploadMethod: 'raw',
dataType: 'json',
success: function (data) {
forge.logging.log("success " + data);
},
error: function (e) {
forge.logging.log("error " + e);
}
});
}

Using fileUploadMethod: 'raw' might be the problem here: it means that the request body is just a binary blob, rather than the multipart-encoded form which your node code expects.
For reference, here is a minimal handler that will save uploaded files into /tmp:
exports.upload = function(req, res){
var filename = '/tmp/' + new Date().getTime();
console.log(JSON.stringify(req.files));
// replace "test" with your actual form field name
fs.readFile(req.files["test"].path, function (err, data) {
fs.writeFile(filename, data, function (err) {
res.send({filename: filename});
});
});
};

Related

Convert byte array into blob (pdf file) and download using angular 5

I'm receiving a byte array from server side and has converted it successfully to blob. However, when I'm trying to download it, it shows the file is corrupted. Below are my codes -
// In client side controller
this.contractsService.downloadPdf(id)
.then((result) => {
var blob = new Blob([result], { type: "application/pdf" });
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "testing.pdf";
link.click();
});
And,
// In client side service
private headers = new HttpHeaders({ 'Content-Type': 'application/json' });
downloadPdf(id: number) {
return this.http.get(this.apiRoutes.download + "/" + id, { headers: this.headers })
.map((res: any) => res)
.toPromise();
}
Any sort of help will be very much appreciated.
Thank you.
Install file-saver
npm i --save file-saver#latest
Your service method
downloadPdf(id: number) {
return this.http
.get(this.apiRoutes.download + "/" + id, { responseType:'blob' })
.toPromise();
}
Now in your component
import { saveAs } from 'file-saver'
this.contractsService.downloadPdf(id)
.then(blob=> {
saveAs(blob, 'testing.pdf');
});
This should do the trick. The HttpClient will now extract the file from the stream. Also have a look in the documentation for blobs with the HttpClient.
In client side service, try explicitly setting the response type of the get request:
downloadPdf(id: number) {
return this.http.get(this.apiRoutes.download + "/" + id, { headers: this.headers; responseType: 'arraybuffer' })
.map((res: any) => res)
.toPromise();
}

Express js render error this.engine is not a function

I'm trying to develop a small API using express. Just want to have 2 views, which, in my case, means 2 html files. One accesing as default with "/" and the other with "/lessons", so we got 2 get controls plus another one which handles every other get input.
*Both files are in the "views" folder and their extension is: *.html
I have no problem accessing the "app.get("/lessons", function..." in fact I know I can acces to that because a simple "console.log(..)" command. The problem is that I got the next error when trying to render:
[TypeError: this.engine is not a function].
Could you help me? I can't understand where is the problem or what I'm doing wrong. I believe it's in the rendering function and has something to do with its configuration and the lessons.html file because index.html has no problem using the same approach.
var express = require('express');
var app = express();
var mod = require('./module');
app.use(express.static('public'));
app.use(express.static('views'));
var port = process.env.PORT || 8080;
app.listen(port, function() {
console.log('Node.js listening on port ' + port);
});
app.get("/", function(req, res) {
console.log("Passed through /");
res.render('index.html');
});
app.get("/lessons", function(req, res) {
console.log("passed through lessons");
res.render('lessons.html', function(err, html) {
if(err) {
console.log(err);
}
res.send(html);
});
//I have tried to to use just: res.render('lessons.html');
});
app.get("*", function(req, res) {
var usageReq = false;
var urlPassed = req.url;
urlPassed = urlPassed.substring(1, urlPassed.length); //remove first "/"
var expected = mod.seeIfExpected(urlPassed); //returns url(if url) or num(if number) or na(if it doesn't match any)
mod.processInfo(expected, urlPassed, function(answer) {
if (answer.found == false && answer.jsonRes == true && answer.info != "inserted") {
res.json({
"error": answer.info
});
} else {
if (answer.jsonRes == true) {
res.json({
"long_url": answer.url,
"short_url": answer.id
});
} else { // go to url
var newUrl = "https://" + answer.url;
res.redirect(newUrl);
}
}
});
});

Expressjs Multer Error Handling with Array

I am using multer's array method as a middleware for my post route and I'm trying to figure out how I can send the error callback from the fileFilter function in the multer options setup as a flash message when the group of files that a user is uploading contains at least one file with the wrong format. My current fileFilter setup achieves this, but instead of sending the user to a blank page with the File Selected is not supported message, I was looking for a way to communicate that to the route using multer as middleware.
Here is my multer setup:
var upload = multer({
storage: multerS3({
s3: s3,
bucket: options.Bucket,
contentType: multerS3.AUTO_CONTENT_TYPE,
acl: options.ACL,
key: function(req, file, cb){
var fileNameFormatted = file.originalname.replace(/\s+/g, '-').toLowerCase();
cb(null, req.user.organizationId + '/' + uploadDate + '/' + fileNameFormatted);
}
}),
fileFilter: function(req, file, cb){
if(!file.originalname.match(/\.(jpg|jpeg|png|gif|csv|xls|xlsb|xlsm|xlsx)$/)){
return cb('File Selected is not supported');
}
cb(null, true);
}
});
Here is my route with upload.array('fileUpload', 5) fileUpload is the name of my file input field and 5 is the multer file length limiting feature.
.post(upload.array('fileUpload', 5), function(req, res) {
//Configure Uploaded S3 File Path strings based on environment for use in DB
var uploadedFiles = req.files;
var s3FilePath = [];
for (var prop in uploadedFiles) {
console.log(uploadedFiles[prop].key);
if (app.get('env') === 'production' || app.get('env') === 'staging') {
s3FilePath = 'https://files.test-site.com/' + uploadedFiles[prop].key;
} else {
s3FilePath.push(uploadedFiles[prop].location);
}
}
models.Blog.update({
blogId: req.body.blogId,
blogDate: req.body.blogDate,
}, {
where: {
userId: req.user.userId,
blogId: req.body.blogId
}
}).then(function(blog) {
console.log('This is the blog ' + blog);
var files = _.map(s3FilePath, function(file) {
console.log(file);
return {
fileName: file,
blogId: req.body.blogId
};
});
return models.BlogFile.bulkCreate(files);
}).then(function() {
res.redirect('/app');
}).catch(function(err) {
res.send(err);
console.log('File Post Error ' + err);
});
});

Connect-busboy - Amazon S3 - Validation and return

I am trying to directly upload files from the browser to Amazon S3 using connect-busboy with the following code (https://github.com/thaume/s3-streaming)
route - index.js:
var awsUpload = require('../services/aws-streaming');
// Index route
// ===========
exports.index = function(req, res){
if (req.method === 'POST') {
return awsUpload(req, function(err, url) {
res.send(JSON.stringify(url));
});
}
res.writeHead(200, { Connection: 'close' });
res.end('<html><head></head><body>\
<form method="POST" enctype="multipart/form-data">\
<input type="file" name="filefield"><br />\
<input type="submit">\
</form>\
</body></html>');
};
with my modified version of aws-streaming.js
// Initialize aws client
// =====================
var config = require('../config/' + 'development');
var Knox = require('knox');
var moment = require('moment');
var crypto = require('crypto');
// Create the knox client with your aws settings
Knox.aws = Knox.createClient({
key: config.aws.AWS_ACCESS_KEY_ID,
secret: config.aws.AWS_SECRET_ACCESS_KEY,
bucket: config.aws.S3_BUCKET_NAME,
region: 'eu-west-1'
});
// S3 upload service - stream buffers to S3
// ========================================
var s3UploadService = function(req, next) {
req.files = {};
req.busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
if (!filename) {
// If filename is not truthy it means there's no file
return;
}
//////////////// CHECK FOR MIMETYE /////////////////////
// If file is not "text/plain" - return //
if (mimetype != "text/plain") {
console.log('true!')
return; // A JSON array with an error "Wrong file type"!
}
// Create the initial array containing the stream's chunks
file.fileRead = [];
file.on('data', function(chunk) {
// Push chunks into the fileRead array
this.fileRead.push(chunk);
});
file.on('error', function(err) {
console.log('Error while buffering the stream: ', err);
});
file.on('end', function() {
// Concat the chunks into a Buffer
var finalBuffer = Buffer.concat(this.fileRead);
req.files[fieldname] = {
buffer: finalBuffer,
size: finalBuffer.length,
filename: filename,
mimetype: mimetype
};
// Generate date based folder prefix
var datePrefix = moment().format('YYYY[/]MM');
var key = crypto.randomBytes(10).toString('hex');
var hashFilename = key + '-' + filename;
var pathToArtwork = '/artworks/' + datePrefix + '/' + hashFilename;
var headers = {
'Content-Length': req.files[fieldname].size,
'Content-Type': req.files[fieldname].mimetype,
'x-amz-acl': 'public-read'
};
Knox.aws.putBuffer( req.files[fieldname].buffer, pathToArtwork, headers, function(err, response){
if (err) {
console.error('error streaming image: ', new Date(), err);
return next(err);
}
if (response.statusCode !== 200) {
console.error('error streaming image: ', new Date(), err);
return next(err);
}
console.log('Amazon response statusCode: ', response.statusCode);
console.log('Your file was uploaded');
next();
});
});
});
req.busboy.on('error', function(err) {
console.error('Error while parsing the form: ', err);
next(err);
});
req.busboy.on('finish', function() {
console.log('Done parsing the form!');
// When everythin's done, render the view
next(null, 'http://www.google.com');
});
// Start the parsing
req.pipe(req.busboy);
};
module.exports = s3UploadService;
What I would like to do is to validate the mimetype and return a json array with the error message, ending the parsing of the form and not upload the file. Have added code to aws-streaming, but it will not return even if validates to true. What have I done wrong?
Also the code runs the callback when it is finished parsing the form, but I would like it to be run when the file is actually uploaded. How can I achieve this, comment out the next() in 'finish' event and move it to the Knox.aws.putBuffer?
Im using Express 4

Streaming an octet stream from request to S3 with knox on node.js

I'm trying to stream an octet-stream straight to S3 using knox on node.js. The octet-stream is an XHR file upload from the browser. I assumed that I could just stream the request into putStream and everything would just work, but alas no.
Here's my code:
var client = knox.createClient({
// AWS credentials here
});
if (req.headers['content-type'].match(/application\/octet-stream/i)) {
var filename = '/'+req.headers['x-file-name'];
client.putStream(req, filename, function(err, res){
// TODO: Catch errors
body = '{"success":"true"}'
res.writeHead(200,
{ 'Content-Type':'text/html'
, 'Content-Length':body.length
})
res.end(body)
});
}
And the error I receive:
TypeError: Bad argument
at Object.stat (fs.js:354:11)
at Client.putStream (./lib/knox/client.js:181:6)
I'm doing something like this:
app.post('/uploadAmazon', function(req, res) {
var params = req.query;
var request = client.request("PUT", '/' + req.header('x-file-name') + '?partNumber=' + params.partNumber
+ '&uploadId=' + params.uploadId, {
'Content-Length' : req.header('Content-Length')
} );
req.on('data', function(data){
request.write(data);
});
request.on('response', function(response) {
console.log('Partial ' + params.partNumber + ' statusCode: ' + response.statusCode);
if (response.statusCode== 200) {
uploadMap[params.id].currentSize++;
uploadMap[params.id].completeXmlArray[+(params.partNumber) - 1] = '<Part><PartNumber>' + params.partNumber + '</PartNumber><ETag>' + response.headers.etag + '</ETag></Part>' ;
if (uploadMap[params.id].currentSize == uploadMap[params.id].totalSize) {
uploadMap[params.id].uploadId = params.uploadId;
completeSend(uploadMap[params.id]);
}
}
}).end();
res.end();
});
Assuming that I receive the file name, part number and uploadId from the post.
I believe client.putStream accepts 4 params, like this:
client.putStream(stream, filepath, {
'Content-Length': file.length,
'Content-Type': 'application/octet-stream',
'x-amz-acl': 'private'
}, function(err, res) {
...
});
If you are using a version of node.js much older than 0.4.5 then upgrade.
Look in the util module and use util.pump to copy the file from the input stream to the output stream. If the file has to be downloaded first, just use a ReadStream from the file as the input stream.
Also, do have a look at the Javascript code for util.pump because I suspect that you haven't quite grasped how async I/O works in node.js.