6

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
    You are using a combination of `await` (promises) and callbacks that I'm not familiar with.. I don't see any references in `multer` to promises, are you sure you are using it right? Do you get any errors when you run it locally? – lucascaro Nov 01 '18 at 05:08
  • This would throw an error if you run it locally `res.redirect` will try to execute first, which would result in `Can't set headers after they are sent to the client` You should think about refactor what the request must process before doing anything on `res`, then respond from within the final promise scope, and catch the error with the `next` callback [link](https://stackoverflow.com/questions/7042340/error-cant-set-headers-after-they-are-sent-to-the-client) – Osama Salama Nov 05 '18 at 15:55

4 Answers4

3

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.

Community
  • 1
  • 1
lazreg87
  • 953
  • 4
  • 16
  • You should use promise in async call and try catch in synchronous code. According to official documentation "try-catch works only for synchronous code. Because the Node platform is primarily asynchronous (particularly in a production environment), try-catch won’t catch a lot of exceptions" https://expressjs.com/en/advanced/best-practice-performance.html – Sohail Nov 07 '18 at 19:34
  • 1
    @Sohail, the original question is using async/await, which is just special syntax to work with promises. To catch exceptions thrown during async/await you would wrap it in a try/catch (check out https://javascript.info/async-await). The official docs of express also reference this link https://strongloop.com/strongblog/async-error-handling-expressjs-es7-promises-generators/#using-es7-asyncawait – lazreg87 Nov 08 '18 at 12:58
0

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)
})
front_end_dev
  • 1,998
  • 1
  • 9
  • 14
0

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.

Community
  • 1
  • 1
antzshrek
  • 9,276
  • 5
  • 26
  • 43
0

It can happen when you use multipart formData and set the header. You can have a CORS like behaviour

Tyler2P
  • 2,324
  • 26
  • 22
  • 31