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

EPIPE from http client when server responds early without reading full request body #54366

Open
StanleyP opened this issue Aug 14, 2024 · 1 comment

Comments

@StanleyP
Copy link

StanleyP commented Aug 14, 2024

Version

v14+

Platform

Linux build-tools 5.4.0-150-generic #167~18.04.1-Ubuntu SMP Wed May 24 00:51:42 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

Subsystem

net,http

What steps will reproduce the bug?

Hello,

this issue is probably related to these existing issues:

#9085
#12339
#38467

I have http client that sends HTTP POST request with quite big payload to upstream haproxy server. This server rejects the request early with 401 Unathorized, does not read the whole request body from client, sends 401 response with 'Connection: close' header. This behavior causes EPIPE on node http client. But the server sends correct response and I need to propagate the response to the client.

Here is my code without the fix:

const http = require('http');
const client = http.request('http://localhost:8008', { method: 'POST' });
client.once('response',(res) => {
  let buf = null;
  res.on('data', (data) => { if(buf) buf = Buffer.concat([buf,data]); else buf = data; });
  res.on('end', () => {
    console.log(res.statusCode+" "+res.statusMessage);
    if(buf) console.log(buf.toString());
  });
});
client.end(Buffer.alloc(10 * 1024 * 1024));

How often does it reproduce? Is there a required condition?

Every time against haproxy server which immediately rejects the request with 401 Unauthorized.

What is the expected behavior? Why is that the expected behavior?

The server response should be returned to the client.

What do you see instead?

EPIPE error.

node:events:495
      throw er; // Unhandled 'error' event
      ^

Error: write EPIPE
    at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:94:16)
Emitted 'error' event on ClientRequest instance at:
    at Socket.socketErrorListener (node:_http_client:501:9)
    at Socket.emit (node:events:517:28)
    at emitErrorNT (node:internal/streams/destroy:151:8)
    at emitErrorCloseNT (node:internal/streams/destroy:116:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
  errno: -32,
  code: 'EPIPE',
  syscall: 'write'
}

Additional information

I tried to fix this behavior with this code, but occasionally I get empty response, so perhaps there is some problem with my code.

Could please someone check it for correctness and suggest improvements or usage of other mechanism how to achieve the same goal (propagate server response to the client in this scenario).

const http = require('http');

const client = http.request('http://localhost:8008', { method: 'POST' });

client.once('socket', (socket) => {
  const socketWriteOrig = socket._write;
  socket._write = (data, encoding, callback) => {
    return socketWriteOrig.call(socket, data, encoding, (err) => {
      if(err && err.code == 'EPIPE') {
        if(callback) callback.call(socket, null);
        return;
      }
      if(callback) callback.call(socket, err);
    });
  }
  const socketWritevOrig = socket._writev;
  socket._writev = (data, callback) => {
    return socketWritevOrig.call(socket, data, (err) => {
      if(err && err.code == 'EPIPE') {
        if(callback) callback.call(socket, null);
        return;
      }
      if(callback) callback.call(socket, err);
    });
  }
});

client.once('response',(res) => {
  let buf = null;
  res.on('data', (data) => { if(buf) buf = Buffer.concat([buf,data]); else buf = data; });
  res.on('end', () => {
    console.log(res.statusCode+" "+res.statusMessage);
    if(buf) console.log(buf.toString());
  });
});

client.end(Buffer.alloc(10 * 1024 * 1024));

IMHO I think that this should be fixed within nodejs http/net module itself. In case that server responds early and with Connection: close it is safe to ignore EPIPE error and abort sending the rest of client request body.

@RedYetiDev
Copy link
Member

RedYetiDev commented Aug 14, 2024

Hey, could you provide a minimal reproducible example that includes the code for the server?

Additionally, can you reproduce in v18, v20, or v22?

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

No branches or pull requests

2 participants