For a test case of the following
curl -X POST --header 'Content-Type: application/json' --header 'Accept: text/html' -d '{ "$class": "org.accordproject.cicero.contract.AccordContractState", "stateId": "test2" }' 'http://domain/path'
Where http://domain/path uses http-proxy-middleware with express then the introduction of app.use(bodyParser.json()) for express with this http-proxy-middleware, all together the app.use(bodyParser.json()) breaks this.
This is an unexpected surprise and finding this cause of this error was difficult.
FYI I worked around this issue like so:
options.onProxyReq = (proxyReq, req: Request, res) => {
if (!req.body || !Object.keys(req.body).length) {
return;
}
const contentType = proxyReq.getHeader('Content-Type');
const writeBody = (bodyData: string) => {
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
proxyReq.write(bodyData);
};
if (contentType === 'application/json') {
writeBody(JSON.stringify(req.body));
}
if (contentType === 'application/x-www-form-urlencoded') {
writeBody(querystring.stringify(req.body));
}
}
contentType maybe contain chareset (contentType: 'application/json; charset:utf-8').
So, should be change contentType === 'application/json' to contentType.includes('application/json')
FYI I worked around this issue like so:
options.onProxyReq = (proxyReq, req: Request, res) => { if (!req.body || !Object.keys(req.body).length) { return; } const contentType = proxyReq.getHeader('Content-Type'); const writeBody = (bodyData: string) => { proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData)); proxyReq.write(bodyData); }; if (contentType === 'application/json') { // contentType.includes('application/json') writeBody(JSON.stringify(req.body)); } if (contentType === 'application/x-www-form-urlencoded') { writeBody(querystring.stringify(req.body)); } }
I instead excluded the proxied requests from the body parser middleware: https://stackoverflow.com/questions/27117337/exclude-route-from-express-middleware
This is my solution: https://github.com/stuartZhang/coexist-parser-proxy
You just need to reorder the middlewares;
put bodyParser after all proxy middlewares and routes without proxy after bodyParser
ex:
app.use('/proxy/routex', myProxy);
app.use('/proxy/routey', myProxy);
... //then, after all proxys
app.use(bodyParser.json());
//and now, all no proxy routes
app.use('/api', (req,res)=>{
//TODO
});
app.get('/api/test', (req,res)=>{
//TODO
});
Same with polka.
@sostenesg7's solution worked.
FYI I worked around this issue like so:
options.onProxyReq = (proxyReq, req: Request, res) => { if (!req.body || !Object.keys(req.body).length) { return; } const contentType = proxyReq.getHeader('Content-Type'); const writeBody = (bodyData: string) => { proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData)); proxyReq.write(bodyData); }; if (contentType === 'application/json') { writeBody(JSON.stringify(req.body)); } if (contentType === 'application/x-www-form-urlencoded') { writeBody(querystring.stringify(req.body)); } }
Saved my soul
Just a use case and slight variations on the work around.
I'm using the Okta Middlware for Node library, which requires body parser, but then I want to include the user's access token in proxied api calls, according to the token relay pattern:
https://developer.okta.com/blog/2020/08/14/spring-gateway-patterns
The requires me to implement the aforementioned fix, but I don't see a requirement to define writeBody as a capture so mine looks like:
// From poxy options
onProxyReq: (targetRequest: ClientRequest, clientRequest: Request) => {
// Headers must be set before doing anything to the body.
if (clientRequest?.userContext?.tokens?.access_token) {
targetRequest.setHeader('Authorization', 'Bearer ' + clientRequest.userContext.tokens.access_token);
}
fixBody(targetRequest, clientRequest);
}
/**
* http-proxy-middleware is not compatible with bodyparser and must appear before it without this fix.
* @see see https://github.com/chimurai/http-proxy-middleware/issues/320
* @param proxyReq
* @param req
*/
function fixBody(proxyReq: ClientRequest, req: Request): void {
if (!req.body || !Object.keys(req.body).length) {
return;
}
const contentType = proxyReq.getHeader('Content-Type') as string;
if (contentType.includes('application/json')) {
writeBody(proxyReq, JSON.stringify(req.body));
}
if (contentType === 'application/x-www-form-urlencoded') {
writeBody(proxyReq, querystring.stringify(req.body));
}
}
function writeBody(proxyReq: ClientRequest, bodyData: string) {
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
proxyReq.write(bodyData);
}
Most helpful comment
FYI I worked around this issue like so: