Repository navigation
ERR_HTTP_HEADERS_SENT from onProxyReq with keep-alive #472
Copy link
Copy link
Description
Under load, when using a keep-alive agent, if the # of incoming requests exceeds the max sockets, we are seeing a ERR_HTTP_HEADERS_SENT very quickly.
Is this a bug report?
Yes
Steps to reproduce
- Configure proxy middleware with a keep-alive agent
- Add an
onProxyReqhandler that sets a header - Apply load with a larger number of threads/VUs than available sockets.
Expected behavior
No errors, perhaps timeouts due to socket availability.
Actual behavior
Hard crash with ERR_HTTP_HEADERS_SENT.
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ClientRequest.setHeader (_http_outgoing.js:485:11)
at ProxyServer.onProxyReq (C:\dev\nwe-jss-test\jss13.0.1-sc9.3.0\keepalive-middleware-test\index.js:23:18)
at ProxyServer.emit (C:\dev\nwe-jss-test\jss13.0.1-sc9.3.0\keepalive-middleware-test\node_modules\eventemitter3\index.js:184:35)
at ClientRequest.<anonymous> (C:\dev\nwe-jss-test\jss13.0.1-sc9.3.0\keepalive-middleware-test\node_modules\http-proxy\lib\http-proxy\passes\web-incoming.js:133:16)
at ClientRequest.emit (events.js:215:7)
at tickOnSocket (_http_client.js:684:7)
at onSocketNT (_http_client.js:723:5)
at processTicksAndRejections (internal/process/task_queues.js:80:21)
Setup
- http-proxy-middleware: 1.0.5, reproducible on 0.2.0 as well
- http-proxy-middleware configuration: see below
- express: 4.16.4
- agentkeepalive: 4.1.3
client info
k6, see below
target server info
IIS
Reproducible Demo
Server
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const keepAlive = require("agentkeepalive");
const apiHost = 'http://localhost';
const agent = new keepAlive({
maxSockets: 120,
maxFreeSockets: 120,
timeout: 60000,
freeSocketTimeout: 30000,
});
const server = express();
server.get('/connections', function(req, res, next) {
res.status(200).send({ sockets: agent.getCurrentStatus() })
});
server.use('*', createProxyMiddleware({
agent,
target: apiHost,
changeOrigin: true,
onProxyReq: (proxyReq, req, res) => {
proxyReq.setHeader('TEST', 'hello world');
}
}));
server.listen(3000);
K6 Project
import http from 'k6/http';
import { Counter } from "k6/metrics";
export let options = {
discardResponseBodies: false,
scenarios: {
headRequest: {
executor: 'constant-vus',
exec: 'headRequest',
vus: 150,
duration: '30s',
}
},
thresholds: {
"pageError": [{threshold: "count<1", abortOnFail: true}],
}
};
const pageError = new Counter("pageError");
export function headRequest() {
var response = http.request('GET', 'http://localhost:3000/');
if (response.error || response.status !== 200) {
pageError.add(1);
}
}
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
nickwesselman commentedon Sep 20, 2020
This appears to be an issue with
node-http-proxy, and the fact that it uses.on('socket')to fire theproxyReqevent. This took a lot of digging into the behavior of HTTP requests in node. When a socket is immediately available to a request, the event fires in theClientRequestconstructor, so the request headers can be modified. But withkeepAliveenabled on the agent, a socket may not be immediately available. In that case the request continues on and begins buffering its output, so by the timeonSocketis invoked, the request header has been 'sent'.I was able to reproduce this without use of
http-proxy:Output on failure
So this isn't a problem with
http-proxy-middleware, but does anyone perhaps have a suggestion as to an alternate way to set headers dynamically based on values from the incoming request?nickwesselman commentedon Sep 25, 2020
Workaround we've found is to instead modify the request headers in separate middleware, before applying the proxy.
bobalong79 commentedon Feb 28, 2021
I've been having the same issue and I think it happens when any agent is used, not something specific to keepalive (I'm using global-agent's HttpsProxyAgent). My workaround is to use the custom filter mechanism to alter the headers prior to the proxy request being set up. It looks like this filter mechanism was designed to allow you to implement custom matching logic, but as it provides access to the
req: IncomingRequestobject before the proxy request is created it is a convenient way to manipulate the headers before they even reach the http-proxy code.I think this is basically the same as what you've done but it keeps all the logic in the same middleware.
I guess one drawback of this approach (and your workaround) is that because the headers are changed on the original incoming request rather than on the proxied request, other downstream routers that rely on "untampered" headers might be compromised. In that case then you'd need to keep a copy of the original set and copy them back after
onProxyRescompletes. I don't need to do that for my application though.JounQin commentedon Sep 14, 2021
I also meet this issue when using
fixRequestBodyfrom this middleware.3rj commentedon Sep 21, 2021
Did you find any workaround to this issue. (using fixRequestBody and agent together)
JounQin commentedon Sep 21, 2021
Nope, I have to move proxy middlewares to be used firstly.
markcellus commentedon Mar 29, 2022
Also ran into this issue with
fixRequestBody😞chestnutchen commentedon Jul 20, 2023
Also ran into this issue with
fixRequestBodyjosslzr commentedon Aug 22, 2023
For those who still facing this issue I've found a workaround. Basically you need to set the headers and buffer properties while creating the proxy middleware:
Hope this helps! :)
kevjames3 commentedon Jan 30, 2025
Thanks to @josslzr for providing the initial inspiration on this as their approach was actually the correct on to take. That being said, I was confused by the implementation for a bit, so I want to put my own take here as my solution required me getting access to the body and
fixRequestBodywas giving me grief.Assuming that you are setting this up in express somewhere to process the body...
And then for the middleware
Explanation:
bufferandheadersand then immediately pass in the called surrounding middleware params making sure we only call the middleware when all params are ready.This makes sure we have no more race condition and that the proxy middleware pulls from the buffer that we specify while having access to the body
bc2009 commentedon Mar 7, 2025
@kevjames3 , can you post a complete working example? In your
proxyReqyou have a oneliner forbody, what do u do after you have thebody, do you need to useproxyReq.write()to write it out?I have the same same error with this discussion. But when I try your code, for me, the proxy looks to get stuck waiting until the sever reset (probably due to timeout cutoff). If I add
proxyReq.end(), I will get an error:My use case is that my target server is an HTTP2 stream service which probably is a proxy either to some internal service or cloud service at like Azure (which is decided by a field in my request).
My case is when proxying to internal service, this proxy middleware works ok. But when proxying to use external cloud like Azure, the
setHeaderwill fail as described at the top of this issue.Not sure if the
HTTP/2stream protocol will have similiar effect likekeep-alivediscussed here (from HTTP/2 rfc looks like connection is persistent). I am thinking to try this http-party/node-http-proxy#1478 (comment) directly to spin up my own proxy server to do customization.chimurai commentedon Jun 18, 2026
Fixed in v4.1, which uses
httpxy(unjs/httpxy#111)Issue was reproducible in v1, v2 and v3 (
http-proxybug)Feel free to re-open a new issue if you are still experiencing issues in the latest version
test(e2e): add HttpAgent keepAlive test (#472)
test(e2e): add HttpAgent keepAlive test (#472) (#1267)
fix(proxy): upgrade http-proxy-middleware and add headersSent guards (#…
fix(web-incoming): defer body piping until socket is assigned