Fix Hapi version 19.0.3 error 415 unsupported media type upload file with multipart/form-data - hapi.js

I searched and couldn't find the right answer.
I seem helpless. But luckily the visua code helped debug the code and I found this line in the index.js#hapi/subtext/lib file
if (contentType.mime === 'multipart/form-data') {
         if (options.multipart === false) {// Defaults to true
             throw Boom.unsupportedMediaType ();
         }
         return await internals.multipart (req, options, source, contentType);
     }
I then fixed multipart = true in router opitions:
{
   payload: {
   maxBytes: 1024 * 1024 * 100,
         // timeout: false, // important
         parse: true,
         output: 'data',
         allow: 'multipart / form-data',
         multipart: true
   }
}
and it worked. Thanks for the visua code debug. I wrote back to someone who might get this error. Know how to handle.
i using hapi version 19.0.3

Form hapi 19 release notes :
Change route options.payload.multipart to false by default
Route configuration default was change to disable multipart processing. You will need to either enable it for the entire server to keep previous behavior or just for the routes where multipart processing is required.
server.route({
method: 'POST',
path: '/submit',
options : {
auth : false,
payload: {
output: 'stream',
parse: true,
allow: 'multipart/form-data',
multipart : true // <== this is important in hapi 19
},
handler: async (req, h) => {
console.log(req);
}
}
});

Related

Hapi js validateFunc session param got null in some requests if i add keepAlive: true or update ttl in onPreResponse Middleware

i upgrade hapi from version 16.1.0 to 20.2.1, and i had to replace catbox-redis package with #hapi/catbox-redis and hapi-auth-cookie package with #hapi/cookie (by using documentation https://hapi.dev/tutorials/auth/?lang=en_US#cookie)
also i remove the depricated hapi-acl-auth package and use the new hapi js feature scope to define roles access routes https://github.com/hapijs/hapi/blob/master/API.md#route.options.auth.access.scope
if the user stills browse on the website, i wanna update TTL option (cookie time to live) by using keepAlive: true option or by using the following middleware onPreResponse by using cookiesAuth option:
server.ext('onPreResponse', function (request, h) {
if (request.auth.isAuthenticated && request.path !== '/logout') {
request.cookieAuth.ttl(TTL);
}
return h.continue;
});
if i remove keepAlive: true, or the middleware, i can browse normaly in the website, but if i add them some of request redirect me to the '/login' page then to homepage
server.auth.strategy('session', 'cookie', {
cookie: {
name: 'cookie_name',
password: 'cookie_password',
ttl: TTL,
isSecure: true,
isHttpOnly: true,
},
redirectTo: '/login',
appendNext: true,
keepAlive: true,
validateFunc: async function (request, session) {
if(session && session.sid) {
try {
let cached = await cache.get(session.sid)
if (!cached || !cached.account) {
return { valid: false };
}
const user = await User.findByPk(cached.account.id, {
include: [
{ model: UserRole, as: 'Roles' },
{ model: EcRole, as: 'EcRoles' }
]
})
if (!user) {
console.log(`----------------------------------------------------------------------- ~ file: index.js ~ line 196 ~ redirect no user`)
return { valid: false }
}
user.scope = await getUserRole(user)
cached.account = user
return { valid: true, credentials: cached.account };
} catch (error) {
console.log(`----------------------------------------------------------------------- ~ file: index.js ~ line 204 ~ error`, error)
return { valid: false };
}
} else {
console.log(`----------------------------------------------------------------------- ~ file: index.js ~ line 210 ~ no session`)
return { valid: false }
}
}
});
by adding some console.log, i find that during the execution of some request, the param session of validateFunc got null value and i get the message 'invalide key'
and also it can be probleme of RedisCache timing during write & read data from cache between 2 requests

How to change status code of headers of response in serverless framwork?

I want to response personal defined statuscode and some headers.
But I find even I change the status code to 201 the status code is still 200. and my defined header is not in headers.
my handler like:
function createResponse(status, header, body) {
return {
headers: Object.assign(header, {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json;charset=utf-8'
}),
statusCode: status,
body: JSON.stringify(body)
}
}
export const hello = async (event, context, cb) => {
const rep = {
message: 'v1.0',
event: event
};
cb(null, createResponse(201, {}, rep));
return;
};
I use serverless.yml my configuration is:
functions:
first:
handler: handlers/first.hello
events:
- http:
method: ANY
path: first
integration: lambda
How to check to correct code can change status code and response header?
I also find my headers still in response body, How to add my header part in Response headers?
If you think my intergration part have problem, can you give me correct configuration example?
Looks like you have a nested object inside headers.
Reference documentation,
https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
Here is the correct sample code to send the correct response,
exports.handler = function(event, context, callback) {
console.log('Received event:', JSON.stringify(event, null, 2));
var res ={
"statusCode": 200,
"headers": {
"Content-Type": "*/*"
}
};
res.body = "Hello, World !";
callback(null, res);
};
Hope it helps.

Hapi send request to current local server

I have a graphql running on my server. And I have an upload route like this:
server.route({
config: {
cors: {
origin: ['*'],
credentials: true
},
payload: {
output: 'stream',
parse: true,
maxBytes: 50869457,
allow: 'multipart/form-data'
},
},
method: ['POST', 'PUT'],
path: '/uploadAvatar',
handler: (request, reply) => {
const data = request.payload;
data.identity = options.safeGuard.authenticate(request);
// REQUEST TO THE SAME SERVER THIS IS RUNNING ON
}
});
I want to send a request to the same server as I am in if that makes sense.. How to do that?
btw I want to call localhost:3004/graphql if it's running on localhost:3004 but on production it's running on port 80.
You can use hapi's built in server.inject method for handling internal routing, the docs for inject are here

Wreck: Getting "Error: Invalid request payload JSON format" when posting a mp4

I am trying to post a mp4 using Wreck to an Hapi end point, but I receive Error: Invalid request payload JSON format. Below are the routes. I suspect that I should specify a content-type of video/mp4 from Wreck, but the doc don't tells how to do that, event the tests. Please help me find out what is wrong there:
{
method: 'POST',
path: '/upload/mov',
handler: function (request, reply) {
const source = path.join(__dirname, '../data/mov.mp4');
const stats = fs.statSync(source);
const fileStream = fs.createReadStream(source);
const req = Wreck.request('post', '/api/upload/mov', {
baseUrl: baseUrl,
payload: fileStream,
maxBytes: 7073741824 // 70mb
});
req.on('response', (res) => {
// code removed to try to isolate the problem
reply({ message: 'finished' });
});
}
},
{
method: 'POST',
path: '/api/uplaod/mov',
config: {
payload: {
maxBytes: 7073741824 // 70mb
}
},
handler: function (request, reply) {
// const stream = request.pipe(request.response);
// code removed to try to isolate the problem
reply({ message: 'sent' });
}
}

Sending Templated emails with node.js, node mailer and nodemailer-mailgun-transport

I have the following basic nodejs app:
var nodemailer = require('nodemailer');
var hbs = require('nodemailer-express-handlebars');
var options = {
viewEngine: {
extname: '.hbs',
layoutsDir: 'views/email/',
defaultLayout : 'template',
partialsDir : 'views/partials/'
},
viewPath: 'views/email/',
extName: '.hbs'
};
var mg = require('nodemailer-mailgun-transport');
var auth = {
auth: {
api_key: ' mailgun api key ',
domain: ' mailgun email domain '
}
}
var mailer = nodemailer.createTransport(mg(auth));
mailer.use('compile', hbs(options));
mailer.sendMail({
from: 'test#inventori.io',
to: 'test#test.com',
subject: 'Any Subject',
template: 'email.body',
context: {
variable1 : 'value1',
variable2 : 'value2'
}
}, function (error, response) {
// console.error(error);
if (error) {
throw error;
};
console.log('mail sent to ',response);
mailer.close();
});
views/email/template.hbs
{{>email/head}}
<body>
{{>email/header}}
{{{body}}}
{{>email/footer}}
</body>
</html>
views/email/email.body.hbs
<h4>Main Body Here</h4>
{{variable1}} <br/>
{{variable2}}
views/partials/email/header.hbs
<h4>Header Content</h4>
views/partials/email/footer.hbs
<h4>Footer Content</h4>
The handlebars template engine gives zero errors but the mailgun transport throws the following error:
Error: Sorry: template parameter is not supported yet. Check back soon!
at IncomingMessage.<anonymous> (~/test/node_modules/nodemailer-mailgun-transport/node_modules/mailgun-js/lib/request.js:228:15)
at IncomingMessage.emit (events.js:129:20)
at _stream_readable.js:908:16
at process._tickCallback (node.js:355:11)
This example uses the gmail node mailer transport:
http://excellencenodejsblog.com/express-nodemailer-sending-mails/
I would like to be able to send templated emails using mailgun.
Any help would be greatly appreciated.
Thank you.
Change your template parameter to html.
If you look at the source code here, the error correct- there is no such thing as a template parameter.
For me, templates weren't rendering properly because I was following an example using extName when the key was actually extname (all lowercase). Perhaps it was renamed overtime and the guide I was looking at is now somewhat out of date.
Full working example below as of 30 May 2020.
Directory Structure:
root/
src/
email-templates/
layouts/
blank.hbs
partials/
hello.hbs
services/
email.service.ts
email.service.ts (Haven't updated this to use proper types yet. Just a poc.)
export async function sendTestEmail() {
try {
// Generate test SMTP service account from ethereal.email
// Only needed if you don't have a real mail account for testing
const testAccount = await nodemailer.createTestAccount()
// create reusable transporter object using the default SMTP transport
const transporter = nodemailer.createTransport({
host: 'smtp.ethereal.email',
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: testAccount.user, // generated ethereal user
pass: testAccount.pass, // generated ethereal password
},
})
transporter.use('compile', hbs({
viewEngine: {
extname: '.hbs', // handlebars extension
partialsDir: 'src/email-templates',
layoutsDir: 'src/email-templates/layouts',
defaultLayout: 'blank',
},
viewPath: 'src/email-templates',
extName: '.hbs'
}))
// send mail with defined transport object
const mailOptions = {
from: 'test#gmail.com', // sender address
to: 'test#gmail.com', // list of receivers
subject: 'Hello ✔', // Subject line
text: 'Hello world?', // plain text body
template: 'hello',
context: {
firstName: 'Clem'
}
}
const info = await transporter.sendMail(mailOptions)
console.log('Message sent: %s', info.messageId)
// Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321#example.com>
// Preview only available when sending through an Ethereal account
console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info))
// Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
} catch (error) {
console.log(error)
}
}