how to upload a file from node.js - file-upload

I found many posts when I queried for this problem, but they all refer to how to upload a file from your browser to a node.js server. I want to upload a file from node.js code to another server. I tried to write it based on my limited knowledge of node.js, but it doesn't work.
function (data) {
var reqdata = 'file='+data;
var request = http.request({
host : HOST_NAME,
port : HOST_PORT,
path : PATH,
method : 'POST',
headers : {
'Content-Type' : 'multipart/form-data',
'Content-Length' : reqdata.length
}
}, function (response) {
var data = '';
response.on('data', function(chunk) {
data += chunk.toString();
});
response.on('end', function() {
console.log(data);
});
});
request.write(reqdata+'\r\n\r\n');
request.end();
})
The above function is called by other code that generates data.
I tried to upload same data file using curl -F "file=#<filepath>" and the upload is successful. But my code fails. The server returns an application specific error which hints that the uploaded file was invalid/corrupt.
I collected tcpdump data and analysed it in wireshark. The packet sent from my node.js code lacks the boundary required for the multipart data. I see this message in wireshark packet
The multipart dissector could not find the required boundary parameter.
Any idea how to accomplish this in node.js code?

jhcc's answer is almost there.
Having to come up with support for this in our tests, I tweaked it slightly.
Here's the modified version that works for us:
var boundaryKey = Math.random().toString(16); // random string
request.setHeader('Content-Type', 'multipart/form-data; boundary="'+boundaryKey+'"');
// the header for the one and only part (need to use CRLF here)
request.write(
'--' + boundaryKey + '\r\n'
// use your file's mime type here, if known
+ 'Content-Type: application/octet-stream\r\n'
// "name" is the name of the form field
// "filename" is the name of the original file
+ 'Content-Disposition: form-data; name="my_file"; filename="my_file.bin"\r\n'
+ 'Content-Transfer-Encoding: binary\r\n\r\n'
);
fs.createReadStream('./my_file.bin', { bufferSize: 4 * 1024 })
.on('end', function() {
// mark the end of the one and only part
request.end('\r\n--' + boundaryKey + '--');
})
// set "end" to false in the options so .end() isn't called on the request
.pipe(request, { end: false }) // maybe write directly to the socket here?
Changes are:
ReadableStream.pipe returns the piped-to stream, so end never gets called on that. Instead, wait for end on the file read stream.
request.end puts the boundary on a new line.

Multipart is pretty complex, if you want to make it look like how a client usually handles "multipart/form-data", you have to do a few things. You first have to select a boundary key, this is usually a random string to mark the beginning and end of the parts, (in this case it would be only one part since you want to send a single file). Each part (or the one part) will need a header (initialized by the boundary key), setting the content-type, the name of the form field and the transfer encoding. Once the part(s) are completed, you need to mark the end of each part with the boundary key.
I've never worked with multipart, but I think this is how it could be done. Someone please correct me if I'm wrong:
var boundaryKey = Math.random().toString(16); // random string
request.setHeader('Content-Type', 'multipart/form-data; boundary="'+boundaryKey+'"');
// the header for the one and only part (need to use CRLF here)
request.write(
'--' + boundaryKey + '\r\n'
// use your file's mime type here, if known
+ 'Content-Type: application/octet-stream\r\n'
// "name" is the name of the form field
// "filename" is the name of the original file
+ 'Content-Disposition: form-data; name="my_file"; filename="my_file.bin"\r\n'
+ 'Content-Transfer-Encoding: binary\r\n\r\n'
);
fs.createReadStream('./my_file.bin', { bufferSize: 4 * 1024 })
// set "end" to false in the options so .end() isnt called on the request
.pipe(request, { end: false }) // maybe write directly to the socket here?
.on('end', function() {
// mark the end of the one and only part
request.end('--' + boundaryKey + '--');
});
Again, I've never done this before, but I think that is how it could be accomplished. Maybe someone more knowledgable could provide some more insight.
If you wanted to send it as base64 or an encoding other than raw binary, you would have to do all the piping yourself. It will end up being more complicated, because you're going to have to be pausing the read stream and waiting for drain events on the request to make sure you don't use up all your memory (if it's not a big file you generally wouldn't have to worry about this though). EDIT: Actually, nevermind that, you could just set the encoding in the read stream options.
I'll be surprised if there isn't a Node module that does this already. Maybe someone more informed on the subject can help with the low-level details, but I think there should be a module around somewhere that does this.

As the error message states you are missing the boundary parameter. You need to add a random string to separate each file from the rest of the files/form-data.
Here is how a request could look like:
The content type:
Content-Type:multipart/form-data; boundary=----randomstring1337
The body:
------randomstring1337
Content-Disposition: form-data; name="file"; filename="thefile.txt"
Content-Type: application/octet-stream
[data goes here]
------randomstring1337--
Note that the -- in the beginning and end of of the random string in the body is significant. Those are part of the protocol.
More info here http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html

The fastest way I was able to do this, that worked, was using the request package. The code was well documented and it just worked.
(For my testing I wanted a JSON result and non-strict SSL - there are many other options...)
var url = "http://"; //you get the idea
var filePath = "/Users/me/Documents/file.csv"; //absolute path created elsewhere
var r = request.post( {
url: url,
json: true,
strictSSL: false
}, function( err, res, data ) {
//console.log( "Finished uploading a file" );
expect( err ).to.not.be.ok();
expect( data ).to.be.ok();
//callback(); //mine was an async test
} );
var form = r.form();
form.append( 'csv', fs.createReadStream( filePath ) );

Related

Converge API Error Code 4000

I am attempting to POST to the Converge Demo API and I am getting a 4000 error. Message is "The VirtualMerchant ID was not supplied in the authorization request."
I am using axios inside Vuex. I am attempting to make the post from Vuex for now since it's demo. I am throwing it up https with TLSv1.2_2018.
Here's the simplified version of the code I am using.
let orderDetails = {
ssl_merchant_id:'******',
ssl_user_id:'***********',
ssl_pin: '****...',
ssl_transaction_type: 'ccsale',
ssl_amount: '5.47',
ssl_card_number: '4124939999999990',
ssl_cvv2cvc2: '123',
ssl_exp_date: '1219',
ssl_first_name: 'No Named Man',
ssl_test_mode: true
}
let orderJSON = JSON.stringify(orderDetails)
let config = {
headers: {
'Access-Control-Allow-Methods': 'PUT, POST, PATCH, DELETE, GET',
'Content-Type': 'application/x-www-form-urlencoded'
}
}
axios.post('https://api.demo.convergepay.com/VirtualMerchantDemo/process.do', orderJSON, config)
.then(res => {
console.log('res', res.data)
})
.catch(e => {
console.log('e', e)
})
Has anyone solved this and/or able to share some wisdom?
I think you are sending the values the wrong way and that's why you receive the message of a missing parameter. The endpoing process.do expects to receive a key value pairs formatted request
ssl_merchant_id=******&ssl_user_id=***********&ssl_pin=****&ssl_transaction_type=ccsale&ssl_amount=5.47&ssl_card_number=4124939999999990&ssl_cvv2cvc2=123&ssl_exp_date=1219&ssl_first_name=No Named Man&ssl_test_mode=true
From Converge website (https://developer.elavon.com)
Converge currently supports two different ways to integrate:
Key value pairs formatted request using process.do (for a single transaction) or processBatch.do (for a batch file) with the following
syntax: ssl_name_of_field = value of field (example: ssl_amount =
1.00)
Or
XML formatted request using processxml.do (for a single transaction) or accountxml.do (for a Admin request), the transaction
data formatted in XML syntax must include all supported transaction
elements nested between one beginning and ending element , the
data is contained within the xmldata variable.

How to upload file in jhipster form?

I am just starting to use jhipster 5 and angular 5. I have a form and in that form in addition to few regular fields, I have a file input.
I could not find any documentation on how to file in jhipster.
EDIT 1:
I could somehow managed to upload file and send to server. Below is my server method to handle the form submission.
#PostMapping("/email-jobs")
#Timed
public ResponseEntity<EmailJobDTO> createEmailJob(MultipartFile file, #Valid #RequestBody EmailJobDTO emailJobDTO) throws URISyntaxException {
log.debug("REST request to save EmailJob : {}", emailJobDTO);
if (emailJobDTO.getId() != null) {
throw new BadRequestAlertException("A new emailJob cannot already have an ID", ENTITY_NAME, "idexists");
}
System.out.println(file.getName() + " File Name ");
EmailJobDTO result = emailJobService.save(emailJobDTO);
return ResponseEntity.created(new URI("/api/email-jobs/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
.body(result);
}
Here i get following exception,
Unsupported Media Type: Content type 'multipart/form-data;boundary=----WebKitFormBoundary73sdwuJtdeRk6xsO;charset=UTF-8' not supported
If I remove #RequestBody from method signature then I dont get above exception but then I start getting 400 bad request exception saying my form fields can not be null.
You must define MultipartFile is #RequestParam and declare produces = MediaType.APPLICATION_JSON_VALUE in post mapping, like:
#PostMapping("/email-jobs", produces = MediaType.APPLICATION_JSON_VALUE)
Client side, you can try send request as this:
Upload.upload({
url: 'api/path',
data: {
file: yourdatafile
},
headers: {'Content-Type': 'multipart/form-data'}
}).progress(function (evt) {
// handle progress
}).success(function (data, status, headers, config) {
// handle success
});
Instead of uploading a file, create a field type as a BLOB and then in your business logic, make a file if u need or else do your.

Get the uploaded file name in play framework 2.5

I'm creating an image upload API that takes files with POST requests. Here's the code:
def upload = Action(parse.temporaryFile) { request =>
val file = request.body.file
Ok(file.getName + " is uploaded!")
}
The file.getName returns something like: requestBody4386210151720036351asTemporaryFile
The question is how I could get the original filename instead of this temporary name? I checked the headers. There is nothing in it. I guess I could ask the client to pass the filename in the header. But should the original filename be included somewhere in the request?
All the parse.temporaryFile body parser does is store the raw bytes from the body as a local temporary file on the server. This has no semantics in terms of "file upload" as its normally understood. For that, you need to either ensure that all the other info is sent as query params, or (more typically) handle a multipart/form-data request, which is the standard way browsers send files (along with other form data).
For this, you can use the parse.multipartFormData body parser like so, assuming the form was submitted with a file field with name "image":
def upload = Action(parse.multipartFormData) { request =>
request.body.file("image").map { file =>
Ok(s"File uploaded: ${file.filename}")
}.getOrElse {
BadRequest("File is missing")
}
}
Relevant documentation.
It is not sent by default. You will need to send it specifically from the browser. For example, for an input tag, the files property will contain an array of the selected files, files[0].name containing the name of the first (or only) file. (I see there are possibly other properties besides name but they may differ per browser and I haven't played with them.) Use a change event to store the filename somewhere so that your controller can retrieve it. For example I have some jquery coffeescript like
$("#imageFile").change ->
fileName=$("#imageFile").val()
$("#imageName").val(fileName)
The value property also contains a version of the file name, but including the path (which is supposed to be something like "C:\fakepath" for security reasons, unless the site is a "trusted" site afaik.)
(More info and examples abound, W3 Schools, SO: Get Filename with JQuery, SO: Resolve path name and SO: Pass filename for example.)
As an example, this will print the original filename to the console and return it in the view.
def upload = Action(parse.multipartFormData(handleFilePartAsFile)) { implicit request =>
val fileOption = request.body.file("filename").map {
case FilePart(key, filename, contentType, file) =>
print(filename)
filename
}
Ok(s"filename = ${fileOption}")
}
/**
* Type of multipart file handler to be used by body parser
*/
type FilePartHandler[A] = FileInfo => Accumulator[ByteString, FilePart[A]]
/**
* A FilePartHandler which returns a File, rather than Play's TemporaryFile class.
*/
private def handleFilePartAsFile: FilePartHandler[File] = {
case FileInfo(partName, filename, contentType) =>
val attr = PosixFilePermissions.asFileAttribute(util.EnumSet.of(OWNER_READ, OWNER_WRITE))
val path: Path = Files.createTempFile("multipartBody", "tempFile", attr)
val file = path.toFile
val fileSink: Sink[ByteString, Future[IOResult]] = FileIO.toPath(file.toPath())
val accumulator: Accumulator[ByteString, IOResult] = Accumulator(fileSink)
accumulator.map {
case IOResult(count, status) =>
FilePart(partName, filename, contentType, file)
} (play.api.libs.concurrent.Execution.defaultContext)
}

Downloading PDF from Dropbox and then uploading to S3 results in larger, corrupt file [duplicate]

I have been struggling to succeed in downloading an image without piping it to fs. Here's what I have accomplished:
var Promise = require('bluebird'),
fs = Promise.promisifyAll(require('fs')),
requestAsync = Promise.promisify(require('request'));
function downloadImage(uri, filename){
return requestAsync(uri)
.spread(function (response, body) {
if (response.statusCode != 200) return Promise.resolve();
return fs.writeFileAsync(filename, body);
})
.then(function () { ... })
// ...
}
A valid input might be:
downloadImage('http://goo.gl/5FiLfb', 'c:\\thanks.jpg');
I do believe the problem is with the handling of body.
I have tried casting it to a Buffer (new Buffer(body, 'binary') etc.) in several encodings, but all failed.
Thanks from ahead for any help!
You have to tell request that the data is binary:
requestAsync(uri, { encoding : null })
Documented here:
encoding - Encoding to be used on setEncoding of response data. If null, the body is returned as a Buffer. Anything else (including the default value of undefined) will be passed as the encoding parameter to toString() (meaning this is effectively utf8 by default).
So without that option, the body data is interpreted as UTF-8 encoded, which it isn't (and yields an invalid JPEG file).

Urlfetch blobs multipart/m

I am trying to use UlrFetch to submit CSV data to Zoho reports. I am using the following code:
function doImport(tabla,file) {
var url="https://reportsapi.zoho.com/api/xxxxxxxx/yyyyyyyyyyy/"+tabla;
var ticket="zzzzzzzzzzzzzzzz" ;//getTicket();
url=url + "?ZOHO_ACTION=IMPORT&ZOHO_OUTPUT_FORMAT=XML&ZOHO_ERROR_FORMAT=json&ZOHO_API_VERSION=1.0"
var params={"ZOHO_API_KEY":"vvvvvvvvvvvvvvvvvvvvvv"
,"ticket":ticket
,"ZOHO_FILE":file
,"ZOHO_IMPORT_TYPE":"APPEND"
,"ZOHO_ON_IMPORT_ERROR":"ABORT"
,"ZOHO_AUTO_IDENTIFY":"true"
,"ZOHO_CREATE_TABLE":"false"
,"ZOHO_DATE_FORMAT":"dd-MM-YYYY"
,"ZOHO_DELIMITER":"0"
};
var options =
{
"method" : "post",
"payload" : params,
"contentType": "multipart/form-data"
};
var response=UrlFetchApp.fetch(url, options);
var tableDataString=response.getContentText();
expireTicket(ticket);
Logger.log(tableDataString);
return tableDataString;
}
However, the data is not submitted in correct multiform format (getting error 500 status). This issue backtracks to early 2011. Please, one or two examples of how to submit blob files in multipart/form-data format would be welcome.
Thanks
For payload, you are passing it as an Object, which looks correct. This will be interpreted as an HTTP form (which you want).
To fix your script, try the following:
Make sure the value you're using for ZOHO_FILE is a Blob. This makes sure the HTTP form will automatically be sent with:Content-Type: multipart/form-data; boundary=[automatically determined]
Do not specify contentType for the HTTP POST. This allows UrlFetchApp to automatically use its own contentType value, which includes the boundary field. (Minor detail: It's ok to still specify contentType on the Blob itself, just not the overall post request. This allows specifying the contentType of each Blob within the post, if that interests you.)
UrlFetchApp will use multipart/form-data encoding automatically if you pass a Blob as a payload value. You may need to use:
"ZOHO_FILE": file.getBlob()