问题
My deployment on Heroku keep crashing on the POST request when I send the mulitpart form. I can't see in the logs if it's on the upload (multer) function, save (mongoose) function or sendMail (nodemailer) function. The only thing I see in the logs is a H18 error: Internal Server.
Router.js
const express = require("express");
const mongoose = require("mongoose");
const router = express.Router();
const multer = require("multer");
const path = require("path");
const File = require("../models/Files");
const mail = require("../handlers/mailer");
// Set storage engine
const storage = multer.memoryStorage();
// Init upload
const upload = multer({
storage: storage
}).single("file");
router.get("/", (req, res) => {
res.render("index");
});
router.post("/send", async (req, res, next) => {
await upload(req, res, async err => {
if (err) {
console.log("error by uploading file:", err);
} else {
console.log(`File is uploaded to the memoryStorage: ${req.file.originalname} `);
}
// Create a model to save in the database
const fileUpload = new File({
fromEmail: "<dk@bigbrother.nl>",
fromName: '"Dennis Klarenbeek 👻"',
email: req.body.email,
subject: req.body.subject,
msg: req.body.msg,
filename: req.file.originalname
});
await fileUpload.save((err, file, rows) => {
if (err) {
console.log("error on saving in the db");
} else {
console.log(`database item has been created: ${file.filename}`);
}
});
// Mail the uploaded attachment
await mail.send({
fromEmail: "dennis.klarenbeek@icloud.com",
fromName: '"Dennis Klarenbeek 👻"',
toEmail: req.body.email,
toName: req.body.name,
subject: req.body.subject,
msg: req.body.msg,
template: "attachment",
attachments: [
{
filename: req.file.filename,
contentType: req.file.mimetype,
content: req.file.buffer
}
]
});
});
res.redirect("/");
});
module.exports = router;
Logs
2018-07-12T15:29:46.104415+00:00 heroku[router]: at=info method=GET path="/css/style.css" host=stormy-ocean-50061.herokuapp.com request_id=57113d1c-9730-40ca-9f41-0d5111854175 fwd="87.251.40.140" dyno=web.1 connect=1ms service=10ms status=304 bytes=237 protocol=https
2018-07-12T15:29:46.103429+00:00 heroku[router]: at=info method=GET path="/css/normalize.css" host=stormy-ocean-50061.herokuapp.com request_id=44a0f90b-1973-4daf-9f40-1e5e5398b9e4 fwd="87.251.40.140" dyno=web.1 connect=1ms service=7ms status=304 bytes=238 protocol=https
2018-07-12T15:29:46.487118+00:00 app[web.1]: [0mGET /webfonts/fa-light-300.woff2 [36m304 [0m0.353 ms - -[0m
2018-07-12T15:29:46.489183+00:00 heroku[router]: at=info method=GET path="/webfonts/fa-light-300.woff2" host=stormy-ocean-50061.herokuapp.com request_id=885eae11-3e4c-4efa-9b60-b3950d9f256d fwd="87.251.40.140" dyno=web.1 connect=1ms service=2ms status=304 bytes=239 protocol=https
2018-07-12T15:29:56.919861+00:00 app[web.1]: [0mPOST /send [36m302 [0m9.041 ms - 46[0m
2018-07-12T15:29:57.100559+00:00 heroku[router]: sock=backend at=error code=H18 desc="Server Request Interrupted" method=POST path="/send" host=stormy-ocean-50061.herokuapp.com request_id=aaafb074-b538-4983-bef1-fa1abf1f2413 fwd="87.251.40.140" dyno=web.1 connect=1ms service=191ms status=503 bytes=234 protocol=https
Does somebody know what this could be?
回答1:
The heroku H18 error is thrown when the socket was destroyed before a response is completed. From heroku docs it states:
Usually, an H18 indicates that a response has multiple stages - for instance, streaming chunks of a large response - and that one of those stages has thrown an error."
https://help.heroku.com/18NDWDW0/debugging-h18-server-request-interrupted-errors-in-nodejs-applications
There are some steps we can do to refactor the code so we use multer as a middleware and to improve the error handling so we can see where the error actually occurs.
To catch errors thrown when resolving an await, you need to wrap it around a try...catch
block. It works exactly like the Promise.catch.
const express = require("express");
const mongoose = require("mongoose");
const router = express.Router();
const multer = require("multer");
const path = require("path");
const File = require("../models/Files");
const mail = require("../handlers/mailer");
// Set storage engine
const storage = multer.memoryStorage();
// Init upload
const upload = multer({
storage: storage
}).single("file");
router.get("/", (req, res) => {
res.render("index");
});
router.post("/send", async (req, res, next) => {
// No need to await this middleware
upload(req, res, err => {
// Refactor to using recommended multer error handling
// https://github.com/expressjs/multer#error-handling
if (err instanceof multer.MulterError) {
// A Multer error occurred when uploading.
console.log("multer error when uploading file:", err);
return res.sendStatus(500);
} else if (err) {
// An unknown error occurred when uploading.
console.log("unknown error when uploading file:", err);
return res.sendStatus(500);
}
console.log(`File is uploaded to the memoryStorage: ${req.file.originalname} `);
// Create a model to save in the database
const fileUpload = new File({
fromEmail: "<dk@bigbrother.nl>",
fromName: '"Dennis Klarenbeek 👻"',
email: req.body.email,
subject: req.body.subject,
msg: req.body.msg,
filename: req.file.originalname
});
// Try executing awaits or catch thrown errors
try {
await fileUpload.save((err, file, rows) => {
if (err) {
console.log("error on saving in the db");
} else {
console.log(`database item has been created: ${file.filename}`);
}
});
// Mail the uploaded attachment
await mail.send({
fromEmail: "dennis.klarenbeek@icloud.com",
fromName: '"Dennis Klarenbeek 👻"',
toEmail: req.body.email,
toName: req.body.name,
subject: req.body.subject,
msg: req.body.msg,
template: "attachment",
attachments: [
{
filename: req.file.filename,
contentType: req.file.mimetype,
content: req.file.buffer
}
]
});
// Return res here to signify end of function execution
return res.redirect("/");
} catch (err) {
console.log('Error occured in saving to DB or with mail send ', err);
return res.sendStatus(500);
}
});
});
module.exports = router;
This should allow you to see the actual error that is occurring. You could of course send the error back in the response as well, right now I'm just sending a status 500 to complete the response.
回答2:
Catch all uncaught exceptions in nodejs to get a better idea where things break.
Add this lines into your nodejs file
process.on('uncaughtException', function (err) {
console.error(err.stack); // either logs on console or send to other server via api call.
process.exit(1)
})
回答3:
I have realized that this error most times comes when one is trying to upload a large file, though heroku said "There is not a limit on filesize for uploads", your app probably got a large file that exceed the Heroku time bond for all requests on the Heroku platform must return the first byte within 30 seconds, it then resulted to the closing of the backend socket, belonging to your app’s web process before the backend returned an HTTP response.
But Heroku gave more reason why you might end up with that error, which I will advice you read for more clarification H18 - Server Request Interrupted
Resolution
The H18 error is similar to the H13 in that both signify that the socket was destroyed before a response was completed. With an H13, the socket was connected, then destroyed without sending any data. An H18 signifies that the socket connected, some data was sent as part of a response by the app, but then the socket was destroyed without completing the response.
Usually, an H18 indicates that a response has multiple stages - for instance, streaming chunks of a large response - and that one of those stages has thrown an error.
To find the error, first check your logs for stack traces near the H18. If you see none, you'll need to look more closely at the handlers for the specific request that's failing. Logging each step of the response, including the x-request-id header, can help.
The backend socket, belonging to your app’s web process was closed before the backend returned an HTTP response.
来源:https://stackoverflow.com/questions/51309616/heroku-crash-over-and-over-by-post-request-multer-or-nodemailer-h-18