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

Axios config headers causes requests to fail with ERR_INVALID_CHAR #5089

Open
lohart13 opened this issue Oct 11, 2022 · 23 comments
Open

Axios config headers causes requests to fail with ERR_INVALID_CHAR #5089

lohart13 opened this issue Oct 11, 2022 · 23 comments

Comments

@lohart13
Copy link

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]
@chrno1209
Copy link

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

@paulhklam1122
Copy link

Thank you for bringing this up @lohart13, we have the exact same issue.

@AntanasGa
Copy link

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.

@mihaiandrei97
Copy link

@Antanas

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.

Thank you for this workaround....i think i debugged this for 3 hours thinking I'm doing something wrong

@minyan-gu
Copy link

same issue here, but for me this error doesn't appear every single time when making the API call.. anyway, is anyone working on a fix yet?

@winston-riley-zocdoc
Copy link

Im seeing this when running tests in jest.

@javiercr
Copy link

javiercr commented Nov 13, 2022

Having the same problem with the latest version of axios (1.1.3), when trying to retry a request.

config.headers = JSON.parse(JSON.stringify(config.headers || {})) as RawAxiosRequestHeaders;

Did the trick as a temporary workaround.

EugeneSnikhovskiy added a commit to EugeneSnikhovskiy/axios that referenced this issue Nov 16, 2022
@EugeneSnikhovskiy
Copy link

Hi everyone.
In my app I have same issue during retry requests with axios-retry.
I try to write a unit test for this case in this repo but bug is not reproduced.
Maybe someone can help. What I missed in my test?

it('should not fail during retries request with headers', function (done) {

    const retries = 100;
    let resRetry = 0;
    server = http.createServer(function (req, res) {
      res.writeHead(resRetry < retries ? 404 : 200);
      resRetry++;
      res.end();
    }).listen(4444, function () {
      const api = axios.create({ baseURL: 'http://localhost:4444' });
      let reqConfig;
      let reqRetry = 0;

      api.interceptors.request.use((config) => {
        config.headers.Authorization = 'xxx';
        return config;
      });

      api.interceptors.request.use((config) => {
        reqConfig = config;
        return config;
      });

      api.interceptors.response.use(null, async (error) => {
        if (reqRetry < retries + 1) {
          reqRetry++;
          return api(reqConfig);
        }
        return Promise.reject(error);
      });

      api.get('/')
          .then(() => done())
          .catch((e) => done(e))
     });
  });

@wwarby
Copy link

wwarby commented Nov 16, 2022

I hit the same issue. Tried the interceptor workaround but it didn't seem to work for me (other kinds of errors got thrown inside the interceptor). I was probably doing it wrong, but needed to resolve quickly so I switched to the package make-fetch-happen instead. I was also using axios-retry.

@AntanasGa
Copy link

@EugeneSnikhovskiy maybe the

api.interceptors.request.use((config) => {
        reqConfig = config;
        return config;
});

Doesn’t get triggered.
Or it’s modified after request (response config being read instead of request config).

Either way try assigning the reqConfig inside response interceptor

@EugeneSnikhovskiy
Copy link

@AntanasGa thanks for response. I tested your suggestion and even try to use axios-retry lib in unit test (with same code as in my project) but test is always succeed

@Naman-Kumar-Sinha
Copy link

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.

I nearly spent a day around this and thought I might be doing something wrong. I was trying to retry the request if the axios timeout set is too small for my async request interceptor to attach some data to my request headers. This hack saved the day. Thanks 🙏🏽

@chengB12
Copy link

do axios team have plan to fix this?

@blingblingdev
Copy link

temporarily fixed with err.config.headers = JSON.parse(JSON.stringify(err.config.headers));

@PetrShchukin
Copy link

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
      },
    });

@mhassan1
Copy link

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

@dileephell
Copy link

Still getting the issues in ^1.3.4 version.

@mikemasam
Copy link

mikemasam commented Mar 8, 2023

same challenge, header had a newline character(\n), remove any existence of \n fixed the issue.
header = header.replace(/\n/g,'');

@simonklimek
Copy link

I have tried several versions of axios from 0.24.0, 1.1.3, 1.2.6 and newest 1.3.4. Every time my Vue 2 app experiencing problems with headers while using Firefox only - DOMException: Invalid header name.

@mikemasam
Copy link

I have tried several versions of axios from 0.24.0, 1.1.3, 1.2.6 and newest 1.3.4. Every time my Vue 2 app experiencing problems with headers while using Firefox only - DOMException: Invalid header name.

@simonklimek have you tried ?
header = header.replace(/\n/g,'');

@simonklimek
Copy link

@mikemasam I'm not sure where to modify the headers. Should I modify headers as a request interceptor or use this replace code somwhere else?

my httpClient.js looks as follows:

import axios from 'axios'
import { ApiSettings } from '@/api/config'
import { authInterceptor } from '@/api/interceptors/authInterceptor'

const httpClient = axios.create(ApiSettings)
httpClient.interceptors.request.use(authInterceptor)
export default httpClient

config.js

export const ApiSettings = {
  baseURL: process.env.VUE_APP_BASE_API_URL,
  headers: new Headers({
    Accept: 'application/json',
    'Cache-Control': 'no-cache',
  })
}

authInterceptor.js

export const authInterceptor = (config) => {
  config.headers['Authorization'] = 'Bearer ' + getAuthToken()
  return config
}

much appreciated your help

@simonklimek
Copy link

My working solution I came up with is replacing object constructor for headers new Headers with:

httpClient.defaults.headers.common['Accept'] = 'application/json'
httpClient.defaults.headers.common['Cache-Control'] = 'no-cache'

@mikemasam
Copy link

mikemasam commented Mar 9, 2023

It seem you had a different issue, maybe new Headers is not a defined object in the current context.
This was my fix.

export const authInterceptor = (config) => {
  config.headers['Authorization'] = 'Bearer ' + getAuthToken().replace(/\n/g,'');
  return config
}

Yanked all new lines since i was reading token from a file edited with a notepad gui application.

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

Successfully merging a pull request may close this issue.