4

I'm using node-http-proxy as middleware. When handling GET requests it's ok. For POST requests with JSON body, I cannot send the application/json header and the body json encoded.

This is my middleware. I have added the reastream option as suggested, but this seems not to solve the issue:

                //restream parsed body before proxying
                proxy.on('proxyReq', function (proxyReq, req, res, options) {
                    if (!req.body || !Object.keys(req.body).length) {
                        return;
                    }
                    var contentType = proxyReq.getHeader('Content-Type');
                    var bodyData;

                    if (contentType === 'application/json') {
                        bodyData = JSON.stringify(req.body);
                    }

                    if (contentType === 'application/x-www-form-urlencoded') {
                        bodyData = queryString.stringify(req.body);
                    }

                    if (bodyData) {
                        console.log("------", bodyData);
                        proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
                        proxyReq.write(bodyData);
                    }
                });
                proxy.on('proxyRes', function (proxyRes, req, res) {
                    modifyResponse(res, proxyRes, function (response) {
                        if (!MXMUtil.empty(response) && !MXMUtil.empty(response.message) && !MXMUtil.empty(response.message.body)) { // valid response
                            if (!MXMUtil.empty(response.message.body.error)) { // error response
                                var message = self.processor.createErrorMessage(500, '', response.message.body.error);
                                response = message;
                            } else { // valid response
                                var message = self.processor.prepareResponse(req_start, response.message.body);
                                response = message;
                            }
                        }
                        return response; // return value can be a promise
                    });
                });
                proxy.on('error', function (error, req, res) {
                    var errorMessage = self.processor.createErrorMessage(500, '', error.message);
                    res.send(errorMessage);
                    res.end();
                });
                proxy.web(req, res, {
                    //  url string to be parsed with the url module
                    target: target_uri,
                    //  true/false, Default: false - changes the origin of the host header to the target URL
                    changeOrigin: true,
                    //  true/false, Default: false - specify whether you want to ignore the proxy path of the incoming request 
                    ignorePath: true
                });

On the backend side I get this error:

Malformed HTTP message from 172.17.0.1: Malformed HTTP headers: '\r\n0\r\n\r\n'

Also the second request will fail with a Can't set headers after they are sent. error on the middleware:

[Tue Sep 25 2018 18:16:29 GMT+0200 (CEST)] trapped error code: message:Can't set headers after they are sent. stack:Error: Can't set headers after they are sent.
    at validateHeader (_http_outgoing.js:491:11)
    at ClientRequest.setHeader (_http_outgoing.js:498:3)
    at ProxyServer.<anonymous> (/webservice/lib/api.js:616:34)
    at ProxyServer.emit (/webservice/node_modules/eventemitter3/index.js:210:27)
    at ClientRequest.<anonymous> (/webservice/node_modules/http-proxy/lib/http-proxy/passes/web-incoming.js:132:27)
    at emitOne (events.js:121:20)
    at ClientRequest.emit (events.js:211:7)
    at tickOnSocket (_http_client.js:652:7)
    at onSocketNT (_http_client.js:668:5)
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
    at process._tickDomainCallback (internal/process/next_tick.js:218:9):

I'm using the body parser before the middleware initialization:

        // express app
        this.app = express();
        // development || production
        this.app.set('env', env);
        // custom logging: only error codes
        this.app.use(morgan('combined', {
            skip: function (req, res) {
                return res.statusCode < 400;
            }
        }));
        // LP: handle Error: request entity too large
        this.app.use(bodyParser.json({limit: '50mb'}));
        this.app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
        this.app.use(cookieParser());
        this.app.use(compression()); // gzip compression

NOTE. This could potentially be a bug and I have opened an issue here, but there should be an alternative approach. Another linked issue is here.

The request raised with curl -d {...} to the tornado backend will work correctly.

[UPDATE]

I have fixed the Can't set headers after they are sent. issue closing the request after write the data:

proxyReq.write(bodyData);
proxyReq.end();

I have tried the suggestion solution to put bodyParser after the proxy use, but I cannot do this in my architecture, this means that in my case the order is like:

        httpProxy = require('http-proxy'),
        proxy = httpProxy.createProxyServer({});
        // custom logging: only error codes
            this.app.use(morgan('combined', {
                skip: function (req, res) {
                    return res.statusCode < 400;
                }
            }));
            // LP: handle Error: request entity too large
            this.app.use(bodyParser.json({ limit: '50mb' }));
            this.app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
            this.app.use(cookieParser());
            this.app.use(compression()); // gzip compression
            //...api routes...
            this.app.post(self._options.baseUrl.uri, function (req, res) {
            var req_start = new Date().getTime();
            var target_uri = self._options.proxy.uri;

            //restream parsed body before proxying
            proxy.on('proxyReq', function (proxyReq, req, res, options) {
                if (!req.body || !Object.keys(req.body).length) {
                    return;
                }
                var bodyData = JSON.stringify(req.body);
                proxyReq.setHeader('Content-Type', 'application/json');
                proxyReq.write(bodyData);
                proxyReq.end();
            });
            proxy.on('proxyRes', function (proxyRes, req, res) {
                modifyResponse(res, proxyRes, function (response) {
                    console.log("--response--->", response)
                    if (!MXMUtil.empty(response) && !MXMUtil.empty(response.message) && !MXMUtil.empty(response.message.body)) { // valid response
                        if (!MXMUtil.empty(response.message.body.error)) { // error response
                            var message = self.processor.createErrorMessage(500, '', response.message.body.error);
                            response = message;
                        } else { // valid response
                            var message = self.processor.prepareResponse(req_start, response.message.body);
                            response = message;
                        }
                    }
                    return response; // return value can be a promise
                });
            });
            proxy.on('error', function (error, req, res) {
                var errorMessage = self.processor.createErrorMessage(500, '', error.message);
                res.send(errorMessage);
                res.end();
            });
            proxy.web(req, res, {
                //  url string to be parsed with the url module
                target: target_uri,
                //  true/false, Default: false - changes the origin of the host header to the target URL
                changeOrigin: true,
                //  true/false, Default: false - specify whether you want to ignore the proxy path of the incoming request 
                ignorePath: true
            });
        });

As noticed in the comments below, it happens the proxied request is sent twice as for the code above, so I can see twice logging in the proxyReq and proxyRes events.

loretoparisi
  • 15,724
  • 11
  • 102
  • 146
  • 1
    I saw this on Github, where you posted the same question. I have to say, that there's a lot going on there. If you use just the first section (`proxy.on('proxyReq'`) only, does that work? I definitely think that keeping it as simple as possible helps. You're definitely sending headers out of sequence, but it's hard for me to see where that could be happening. You're probably returning twice by responding in middleware and then continuing out of the middleware to respond again (I've had that happen too, in middleware). That would cause it. – Kevin Teljeur Sep 26 '18 at 09:41
  • @KevinTeljeur thanks for point this out. Let me update the question. – loretoparisi Sep 26 '18 at 09:51
  • @KevinTeljeur I have updated the question with a fix for the headers, but the overall issue is still there. I think you are write it seems I have twice the response, but I donno where in the code this is caused. – loretoparisi Sep 26 '18 at 10:14
  • As I say, it seems very convoluted to me, what you are doing there. You need to really strip it down to where it works and build it up again. I think that you might be doing too much inside those proxy event handlers, which is tripping everything up, particularly in end responses. As far as I know, it is designed to pass through the response and request to the parent router/server and you might be modifying the expected behaviour too much. – Kevin Teljeur Sep 27 '18 at 10:57
  • 1
    Without setting 'Content-Length' my request was hanging, so the question is helpful :) – Pavel Biryukov Feb 05 '21 at 14:20
  • 1
    "the proxied request is sent twice" - in my case first request is CORS pre-flight, so I added my own CORS response with proper headers like app.use(function_to_generate_CORS_response_on_OPTIONS_request) – Michal.S Jun 21 '22 at 14:13
  • @Michal.S good point! So the `CORS` pre-flight response will look like https://stackoverflow.com/a/8689332/758836 – loretoparisi Jun 23 '22 at 16:09

0 Answers0