server.inject() misinterprets payload objects containing both JSON data and buffer data. The buffer object is converted into a large array of integers, rather than kept as a buffer or binary data.
Background: I need to upload an image with some JSON data in the same request.
Here's the route's validation schema:
validate: {
payload: {
json: O({
name: S().min(1).max(50).required(),
filter: O().required()
}).required(),
image: Joi.binary()
}
This data can be retrieved in the handler function by referencing request.payload.json and request.payload.image. request.payload.json is a simple JSON object and request.payload.image is a buffer object.
The actual request from the browser has a content-type of multipart/form-data and looks like:
Content-Disposition: form-data; name="json"; filename="blob"
Content-Type: application/json
{ "name": "Red stuff", "filter": { "colour": "red" }
------WebKitFormBoundaryI8wGLTaJyPZAAnD5
Content-Disposition: form-data; name="image"; filename="image.jpg"
Content-Type: image/jpeg
<BINARY IMAGE DATA>
------WebKitFormBoundaryI8wGLTaJyPZAAnD5--
This is interpreted correctly.
When using server.inject() as so:
var url = 'http://127.0.0.1/filters';
var method = 'POST';
var payload = {
json: {
name: 'Red stuff',
filter: {
colour: 'red'
}
},
image: require('fs').readFileSync('image.jpg')
};
server.inject({url: url, method: method, payload: payload }, function(res) {
console.log(res);
});
validation fails because the data is not binary data. When digging further, the received data is:
{
json: {
name: 'Red stuff',
filter: {
colour: 'red'
}
},
image: [255,
216,
255,
1,
...
]
}
You can't just put binary data as a payload property. The way I've been handling this is using form-data to construct a multipart payload and then stream-to-promise to buffer the multipart stream and pass it to inject.
Yep, as @bendrucker said, you need to use form-data to construct your payload buffer.
Is there any example how to use the libraries with hapi together?
Here's a quick snippet based on samples from form-data that should get you started :
var FormData = require('form-data');
var streamToPromise = require('stream-to-promise');
var form = new FormData();
form.append('my_field', 'my value');
form.append('my_buffer', new Buffer(10));
form.append('my_file', fs.createReadStream('/foo/bar.jpg'));
streamToPromise(form).then(function(payload) {
server.inject({ url: url, method: method, payload: payload }, function(res) {
....
});
});
perfect thanks very much, just to have this fully working there needs to be passed also the headers of the form. Here is updated code with the headers:
var FormData = require('form-data');
var streamToPromise = require('stream-to-promise');
var form = new FormData();
form.append('my_field', 'my value');
form.append('my_buffer', new Buffer(10));
form.append('my_file', fs.createReadStream('/foo/bar.jpg'));
var headers = form.getHeaders();
streamToPromise(form).then(function(payload) {
server.inject({ url: url, method: method, payload: payload, headers: headers }, function(res) {
....
});
});
@petermilan really need headers
The following leaks were detected: @ any-promise/REGISTRATION using above:
streamToPromise(form).then(function(payload)
FYI you can use https://www.npmjs.com/package/get-stream snippet below to help any other soul with the same issue
const GetStream = require('get-stream');
const form = new FormData();
form.append('file', Fs.createReadStream(Path.join(__dirname, 'filename')));
const request = {
method,
url,
headers: form.getHeaders()
};
request.payload = await GetStream(form);
const response = await internals.server.inject(request);
This thread has been automatically locked due to inactivity. Please open a new issue for related bugs or questions following the new issue template instructions.
Most helpful comment
perfect thanks very much, just to have this fully working there needs to be passed also the headers of the form. Here is updated code with the headers: