axios: Axios config headers causes requests to fail with ERR_INVALID_CHAR

Describe the bug

When using the error.config property of an AxiosError to resubmit a request, the below error is thrown. Prior to version 1.x, this solution worked perfectly.

TypeError [ERR_INVALID_CHAR]: Invalid character in header content ["0"]
    at ClientRequest.setHeader (node:_http_outgoing:647:3)
    at new ClientRequest (node:_http_client:284:14)
    at Object.request (node:http:97:10)
    at RedirectableRequest._performRequest (test\node_modules\follow-redirects\index.js:284:24)
    at new RedirectableRequest (test\node_modules\follow-redirects\index.js:66:8)
    at Object.request (test\node_modules\follow-redirects\index.js:523:14)
    at dispatchHttpRequest (test\node_modules\axios\dist\node\axios.cjs:2327:21)
    at new Promise (<anonymous>)
    at httpAdapter (test\node_modules\axios\dist\node\axios.cjs:2060:10)
    at Axios.dispatchRequest (test\node_modules\axios\dist\node\axios.cjs:3158:10) {
  code: 'ERR_INVALID_CHAR'
}

After looking through the code, it appears that there are two issues at play. The first one is that when an existing instance of AxiosHeaders is passed as a parameter to the new AxiosHeaders function, the headers from the previous instance are not correctly read.

The second issue is there when the config passed to the axios function is merged with the default headers, the output header object fails to merge with the default header object as the header object in the config passed to the axios function is an instance of AxiosHeaders not a plain object.

Sample header instance from the error.config.headers

AxiosHeaders {
  'X-Custom-Header': 'foobar',
  'User-Agent': 'axios/1.1.2',
  'Accept-Encoding': 'gzip, deflate, br',
  [Symbol(defaults)]: { Accept: 'application/json, text/plain, */*' }
}

Sample header instance that causes the error to be thrown

AxiosHeaders {
  '[object Object]': undefined,
  [Symbol(defaults)]: { '0': [Function: get] }
}

To Reproduce

Please see the code snippet below to reproduce the issue (or on RunKit).

const axios = require('axios');
const express = require('express');

const app = express();

app.get('/500', (req, res) => {
	res.status(500).send('Internal Server Error');
});

app.listen(3000, () => {
	console.log('Server is running on port 3000');
});

const test500 = async () => {
	try {
		const response = await axios.get('http://localhost:3000/500', {
			headers: { 'X-Custom-Header': 'foobar' },
		});
		console.log(response.data);
	} catch (error) {
		console.log(error.config.headers);
		await axios(error.config);
	}
};

test500();

Expected behaviour

In the above example, the expected outcome is that the request would fail again with an error code 500, however in a real-world scenario, you could wait for x amount of time before retrying the request again.

Environment

  • Axios Version [1.x.x]
  • Adapter [HTTP]
  • Node.js Version [18.10.0]
  • OS: [Windows 11, Ubuntu 22.04]

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 24
  • Comments: 23

Commits related to this issue

Most upvoted comments

Experiencing the same issue, v1.1.3, tho tested on browser(firefox) / react, the error i got: DOMException: Invalid header name.. Found you can use the dumb json conversion to work around this issue like:

// [ ... ]
axiosInstance.interceptors.response.use(
  undefined,
  (err) => {
    // [ ... ]
    const config = err.config;
    // config.headers is possibly undefined
    config.headers = JSON.parse(JSON.stringify(config.headers || {})) as RawAxiosRequestHeaders;
    // [ ... ]
    return axiosInstance(config);
  },
);

when checked

{
  "Content-Type": null,
  Symbol(defaults): Object { Accept: "application/json, text/plain, */*" },
  // [ ... ]
}

transformed to:

{
  Accept: "application/json, text/plain, */*",
  // [ ... ]
}

not sure where the Content-Type went off to. Whilst sketchy it works.

Having the same issue with axios-retry library! “axios”: “1.2.1”, “axios-retry”: “3.3.1”,

Came up with the following fix.

axiosRetry(axiosInstance, {
      retries: options?.retries ?? 2,
      shouldResetTimeout: true,
      retryDelay: (retryCount: number) => {
        // Retry every 1000ms * number of tries + jitter (10ms to 1000ms)
        return options?.retryDelay ?? retryCount * 1000 + Math.floor(Math.random() * Math.floor(100)) * 10;
      },

     // The fix is here
      onRetry: (retryCount, error, requestConfig) => {
        requestConfig.headers = JSON.parse(JSON.stringify(error.config.headers));
      },
      retryCondition: (error: any) => {
        // here's my condition
      },
    });

I am having the same issue, had to downgrade to version below 1.0.0

This appears to be fixed in axios@1.2.0 and higher.