X Tutup
The Wayback Machine - https://web.archive.org/web/20201123223713/https://github.com/expressjs/body-parser/issues/207
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

body-parser.json middleware holding unto request until expressjs request timeout 120 seconds #207

Open
cross311 opened this issue Nov 20, 2016 · 21 comments
Assignees
Labels

Comments

@cross311
Copy link

@cross311 cross311 commented Nov 20, 2016

I am running performance tests against an expressjs application. We are using new relic to track down performance issues. We are noticing that Middleware: jsonParser is the last method ran into on the newrelic traces that last for 120 seconds (our expressjs reqeust timeout time).

Looking at our logs we can see raw-body getting abort called on it. Which makes sense because expressjs is aborting the request because it is hitting the 120 cut off.

{ [Error: request aborted]
  code: 'ECONNABORTED',
  expected: 85,
  length: 85,
  received: 0,
  message: 'request aborted',
  status: 400,
  statusCode: 400,
  type: 'request.aborted' } 'Error: request aborted\n    at IncomingMessage.onAborted (D:\\home\\site\\wwwroot\\node_modules\\body-parser\\node_modules\\raw-body\\index.js:269:10)\n    at IncomingMessage.<anonymous> (D:\\home\\site\\wwwroot\\node_modules\\continuation-local-storage\\context.js:84:17)\n    at emitNone (events.js:72:20)\n    at IncomingMessage.emit (events.js:166:7)\n    at IncomingMessage.wrapped (D:\\home\\site\\wwwroot\\node_modules\\newrelic\\lib\\transaction\\tracer\\index.js:183:28)\n    at IncomingMessage.wrappedEmit (D:\\home\\site\\wwwroot\\node_modules\\newrelic\\lib\\transaction\\tracer\\index.js:220:46)\n    at IncomingMessage.emitted [as emit] (D:\\home\\site\\wwwroot\\node_modules\\continuation-local-storage\\node_modules\\emitter-listener\\listener.js:122:21)\n    at abortIncoming (_http_server.js:280:11)\n    at Socket.serverSocketCloseListener (_http_server.js:293:5)\n    at emitOne (events.js:82:20)\n    at Socket.emit (events.js:169:7)'

How is it that we are still in this readStream method after that long? Have you seen cases of this and maybe could point me in a direction to look for some bad request I am sending in?

Right now this is our biggest hold up because this is holding onto a high enough number of requests that it is affecting our 99%. Looking through all the json parsing code everything in your module has try catch around it, it is just this stream is never ending.

Maybe I need to post this in the raw-body npm package github issues, but I am going through this module.

@dougwilson
Copy link
Member

@dougwilson dougwilson commented Nov 20, 2016

How is it that we are still in this readStream method after that long?

I'm not sure,

Have you seen cases of this and maybe could point me in a direction to look for some bad request I am sending in?

The only cases I've heard of is that the request was incomplete.

Can you provide information on how to reproduce the issue? That's probably the only way I would be able to help figure it out, if I could reproduce it.

@cross311
Copy link
Author

@cross311 cross311 commented Nov 20, 2016

I was thinking it was related to this question: #112 but we are logging it in our error handler, just not seeing it until the request has been around for 120 seconds.

@dougwilson
Copy link
Member

@dougwilson dougwilson commented Nov 20, 2016

Your error above shows that "expected: 85" and "received: 0" which means that there must be Content-Length: 85 header on the request and either you have code in your app that is reading the request before this module or the client never sent a single byte to the server.

@dougwilson dougwilson self-assigned this Nov 20, 2016
@cross311
Copy link
Author

@cross311 cross311 commented Nov 20, 2016

@dougwilson thanks for the quick reply. Ok so look for something that is reading before this middleware. This could be possible because we only invoke this on api calls.

@dougwilson
Copy link
Member

@dougwilson dougwilson commented Nov 20, 2016

Currently this module requires setting req._body = true when something reads the request so this module knows just to skip reading the request body, since otherwise it's hard to tell if it has been read or not. I have been testing an alternative method to detect this on the 2.x branch, but had some issues, so it hasn't been promoted into a release yet, so usually that is the main cause for a stalled request inside this middleware, which can be worked-around by setting req._body = true anywhere in your app you're reading the request body so this middleware knows it can't read it any longer.

@dougwilson
Copy link
Member

@dougwilson dougwilson commented Nov 20, 2016

Maybe I need to post this in the raw-body npm package github issues, but I am going through this module.

Talking here is fine since I happen to be maintaining both, so no reason to create an extra issue :)

@cross311
Copy link
Author

@cross311 cross311 commented Nov 20, 2016

I am looking through all our middleware at the moment that gets called before this one. I do not see that any are reading the body, just looking at header values.

Could this happen if the client sent headers but has not yet sent the body, or somehow the body is lost?

I know a response can work like that, but do not know if a request can work like that. I guess if this is possible I will need to look through our infrastructure to see why the body portion could be lost.

@dougwilson
Copy link
Member

@dougwilson dougwilson commented Nov 20, 2016

Could this happen if the client sent headers but has not yet sent the body, or somehow the body is lost?

Yea, if the client sends the Content-Length: 85 header but no body, then this would happen.

I know a response can work like that, but do not know if a request can work like that.

It can, which if of course an invalid request, but it can still happen if something is emitting an invalid request.

I guess if this is possible I will need to look through our infrastructure to see why the body portion could be lost.

A good place to start is just do a packet capture right at the Node.js server. This is a quick way to tell you if the issue is inside Node.js code somewhere or outside the code.

@cross311
Copy link
Author

@cross311 cross311 commented Nov 20, 2016

What do you recommend for packet capture in the node server? Sorry for my ignorance.

@dougwilson
Copy link
Member

@dougwilson dougwilson commented Nov 20, 2016

I always use https://www.wireshark.org/ personally. It works on all platforms.

@cross311
Copy link
Author

@cross311 cross311 commented Nov 20, 2016

That puts me in a bad spot, running on azure web apps, which will not allow me to put this in front of my nodejs server. We are testing a performance environment that matches our production environment. Will see if I can get this to happen locally.

EDIT: Azure has logging for this I can turn on :(.

@cross311
Copy link
Author

@cross311 cross311 commented Nov 21, 2016

Update: We were able to track down that if traffic is not sent through our application firewall we do not see the issue of headers but no body.

After we removed the application firewall from the flow on our performance tests, we were able to get back up to our known limit.

Is this an error case we might be able to bubble up in the error message in the body-parser? Seems like it might be not an easy thing, since a user could also abandon the request causing the abandon to be called not just the expressjs hitting request timeout. If you believe this is something you would be interested in I can open a pull request against body-raw for.

@dougwilson
Copy link
Member

@dougwilson dougwilson commented Nov 21, 2016

Awesome that you found your issue! What was the wording of the error you're thinking of? I don't think there is any way to actually determine anything from within these modules / Node.js other than that a timeout occurred while trying to wait for the body to arrive. What do other web servers say under these conditions?

@cross311
Copy link
Author

@cross311 cross311 commented Nov 23, 2016

Given the abandoned error I would not want to mess with that one. However thinking about this is there a way we can set a wait for body timeout on that stream? If we can set that then we can do a specific error on did not receive full body within timeout. Writing this while thinking about t on my phone when I get back to my computer I will take a look of this already exists.

@dougwilson
Copy link
Member

@dougwilson dougwilson commented Nov 23, 2016

It does not exist outside of the socket timeout from Node.js you can set. I have an open issue about adding this, but it is more complicated than I think you realize. Apache and nginx are good examples of how to implement it correctly, so we want to add the same method for calculating this that those servers use.

@cross311
Copy link
Author

@cross311 cross311 commented Nov 23, 2016

awesome! I agree I am just looking at this through the lens of my current issue. Thank you tons for your insights on this. It has really helped us track down this issue and keep some things in mind for future. I will keep an eye for any changes.

@russitto
Copy link

@russitto russitto commented Nov 1, 2018

any news???

@dougwilson
Copy link
Member

@dougwilson dougwilson commented Nov 1, 2018

It's on my backlog of things to do, but I have not made it to this task still. Anyone is welcome to make a pull request with an implementation since the source code is open. I can bet that would end up la ding much sooner than just waiting for me to get to it 👍

@kmcnerney-salesforce
Copy link

@kmcnerney-salesforce kmcnerney-salesforce commented Apr 10, 2019

@cross311 I have the same issue as you. It's very rare, so most requests succeed, but some hang until being aborted. At that point, I can see expected bytes > 0 but received bytes == 0. Can you give any details on what application firewall you needed to turn off, or any other details about this issue that aren't in this thread?

@dougwilson dougwilson mentioned this issue Aug 15, 2019
@kirillgroshkov
Copy link

@kirillgroshkov kirillgroshkov commented Aug 16, 2019

Just sharing here how we overcame the same issue (no timeout when client sends Content-Length header with size higher than actual payload size).

I kind of expected such feature to be a part of bodyParser, but didn't find any.

I've created 2 middlewares - one to put before bodyParser (that sets the timeout) and one after (that removes the timeout when bodyParser was executed successfully):

app.use(bodyParserTimeout())
app.use(express.json())
app.use(clearBodyParserTimeout())

And implementation is (very simple timeout/clearTimeout, no magic):

/**
 * Should be called BEFORE bodyParser
 */
export function bodyParserTimeout (cfg: BodyParserTimeoutCfg = {}): RequestHandler {
  const { timeoutSeconds, httpStatusCode, httpStatus } = {
    timeoutSeconds: 10,
    httpStatusCode: 400,
    httpStatus: 'Timeout reading request input',
    ...cfg,
  }

  const timeout = timeoutSeconds * 1000

  return (req: RequestWithTimeout, res, next) => {
    req._bodyParserTimeout = setTimeout(() => {
      if (res.headersSent) return

      respondWithError(
        req,
        res,
        new HttpError(httpStatus, {
          code,
          httpStatusCode,
          userFriendly: true,
        }),
      )
    }, timeout)

    next()
  }
}

/**
 * Should be called AFTER bodyParser
 */
export function clearBodyParserTimeout (): RequestHandler {
  return (req: RequestWithTimeout, res, next) => {
    if (req._bodyParserTimeout) clearTimeout(req._bodyParserTimeout)
    next()
  }
}
@akc42
Copy link

@akc42 akc42 commented Feb 29, 2020

I have had this issue for a while where specific api calls in my application would hang for two minutes . I've spend the morning finding out what is happening and it appears to be this issue reported here. Its not entirely consistent - although I can effectively make it so by clean loading everything (ie starting the server from scratch and then reloading the client) and then it occurs about 80% of the time. Using node 12.16.1 and using nodes built in http2 server.

This snippet of code is where the problem is (in an enormous and non-public application). Since it is related to logon - a few things have happened with get requests, but this is the first attempt at a POST request and therefore the first time that bodyParser.json() has been called (its used elsewhere in the middleware chain once the client has logged on.

      const parser = bodyParser.json();
      debug('set up /logon route');
//      router.route('/logon').all(bodyParser.json()).post((req,res) => {
      const logonRouter = router.route('/logon');
      logonRouter.all((req,res,next) => {
        try {
          debug('about to parse body');
          parser(req,res,(err) => {
            if (err) {
              debug('had logon body error');
            } else {
              debug('logon parsed body ok');
            }
            next();
          });
        } catch(e) {
          debug('error in body parser ', e);
        }
      }).post((req, res) => {
        debug('logon request');

I see the debug statement "about to parse body", but the callback is never called.

I am not sure how important the "first time bodyParser.json()" has been called is. Elsewhere in my application I have an api which is called once a minute once the user has logged on and that is also sometimes hanging. However I cannot at yet reproduce it at will, and it does involve a quite long running database query every so often, which might be the reason its timing out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
6 participants
You can’t perform that action at this time.
X Tutup