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

Response timeout does not work as expected #459

Closed
olalonde opened this issue Sep 29, 2016 · 20 comments
Closed

Response timeout does not work as expected #459

olalonde opened this issue Sep 29, 2016 · 20 comments

Comments

@olalonde
Copy link

olalonde commented Sep 29, 2016

The following code doesn't work as expected (no timeout error is fired):

 client.get('/stop-writing-body-halfway', {
    timeout: 100,
 })

After a few minutes, a response is resolved despite Content-Length not having been reached and the server not explicitly closing the connection. Edit: never mind the connection is probably closed after 2 mins because of https://nodejs.org/api/http.html#http_server_timeout

My server never closes the connection and stops writing the body half way:

  const stopWritingBodyHalfway = (msg, res) => {
    res.setHeader('Content-Length', 500)
    const body = Buffer.alloc(250, 'a')
    res.write(body)
  }

Is the timeout only for the request, not for the response?

@olalonde
Copy link
Author

Temporary workaround:

var Timeout = require('stream-timeout')

  client.get('/stop-writing-body-halfway', {
    responseType: 'stream',
  }).then((response) => {
    const { data } = response
    return new Promise((resolve, reject) => {
      const bufs = []
      data
        .pipe(new Timeout(200))
        .on('timeout', () => {
          data.req.abort()
          const err = new Error('timeout')
          err.data = Buffer.concat(bufs)
          reject(err)
        })
      data.on('data', (buf) => {
        bufs.push(buf)
      })
      data.on('end', () => resolve(Buffer.concat(bufs)))
    })
  })

@nickuraltsev
Copy link
Member

In the current implementation, the timeout is canceled when the response headers have been received, but any suggestions on how to improve this are welcome.

@dtryon
Copy link

dtryon commented Oct 11, 2016

Shouldn't the timeout be cleared on stream.on('end'?
What would be the downside of waiting until the entire response has been received before clearing?

https://github.com/mzabriskie/axios/blob/master/lib/adapters/http.js#L161

@olalonde
Copy link
Author

@dtryon I'm not sure I understand what you mean. My request was that we have a timeout for response bodies because sometimes servers shutdown unexpectedly and you don't want to be forever stuck waiting for a response that will never arrive.

@dtryon
Copy link

dtryon commented Oct 12, 2016

@olalonde Yes, exactly. At the moment, the current timeout is cleared before the response body has been completely received:

https://github.com/mzabriskie/axios/blob/master/lib/adapters/http.js#L116

The timeout gets cleared before the body stream is processed.

Instead the timeout could be be cleared later (when the body has been completely received).

@ancyrweb
Copy link

Any solution for this ? I'm having the same problem, response data is halfway received but after the defined timeout, it just stops and there are no signal a timeout has been triggered.

Usually we want timeout to be applied on request. If the data start to be fetched, timeout shouldn't apply (or might we use a responseTimeout variable in the config ?).

@ghost
Copy link

ghost commented Oct 26, 2016

@Rewieer @ALL, Even I was facing this issue. I used to stop the server where I send API requests and then I used to make a request. It used to take minutes for displaying the error message.

Then I found a solution provided by someone in the issues. I do not remember the issue number or title. (Please attach the issue here if someone recognizes the title of the issue from the code I am providing)

This is what I did:

// Rough implementation. Untested.
// This is the code provided by some angel
export function timeout(ms, promise) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      reject(new Error("timeout"));
    }, ms);
    promise.then(resolve, reject);
  });
}

// This is how I implemented it in my code
    return timeout(5000, axios.get(`${ROOT_URL}/customer/${id}`)).then((response) => {

        if(response.status === 200) {

          // Dispatch the success action
          dispatch(receiveAddr());

          return response;
        }
      }).catch(err => {

        // If there was a problem, we need to
        // dispatch the error condition
        if(err.data && (err.status >= 400 && err.status <= 600)) {
          dispatch(errAddr(err.data));
        } else {
          dispatch(errAddr('Please check your network connection and try again.'));
        }

        return err;
      });

@olalonde
Copy link
Author

On a related note, I wrote https://github.com/blockai/broken-http-server to help test those sort of things in my code. Could be helpful in axios tests.

@ancyrweb
Copy link

@srahulprdxn sounds very good for request timeout, but any clue for response timeout ? What if I receive a success response ? It will trigger the timeout, see the success status code and just consider things are done even through the JSON isn't entirely parsed.
Even worst, sometimes it just magically parse the JSON but it's only a fraction of data.

@ancyrweb
Copy link

ancyrweb commented Oct 26, 2016

I've come to this solution.

const defaultConfig = {
  method: 'GET',
  params: null,
  data: null,
  timeout: 2000
};

const fetch = (url, method, config) => {
  const finalConfig = Object.assign({}, defaultConfig, config);
  return axios({
    url,
    method,
    params: finalConfig.params, // QUERY
    data: finalConfig.data, // REQUEST
    headers: finalConfig.headers,
    timeout: finalConfig.timeout,
  }).then(response => {
    if(response.headers['content-length'] > response.request._response.length){
      response.__incomplete = true;
    }

    return response;
  });
};
`

@dtryon
Copy link

dtryon commented Oct 26, 2016

@srahulprdxn This is interesting, but unless I'm mistaken, this solution will not abort the underlying request (and socket).

@ancyrweb
Copy link

@srahulprdxn The post relating the issue thing is #56

@ghost
Copy link

ghost commented Oct 30, 2016

@Rewieer Awesome. Thanks a lot for finding this post. I had any issues with response. Hence, can't comment anything on that. Sorry.

@teb02
Copy link

teb02 commented Aug 1, 2017

Faced with the same issue (axios 0.12.0).

I am working with the server that doesn't close connections properly and as a result - axios' Promise never resolves (even with timeout being set). I solved the problem wrapping axios request by own Promise and rejecting it by own timeout.

I believe timeout should reject promise, counting in total request + response time. I mean if timeout is 5 secs, request took 3 secs and the response is receiving more than 2 secs - timeout should break this axios request. Is there any chance the behavior of axios timeout would be changed? :)

@evenfrost
Copy link

Happens for me from time to time on different requests even if timeout is set.

@emilyemorehouse emilyemorehouse added this to In progress in 0.19.0 Feb 21, 2018
@hiranya911
Copy link

We've been using the following trick in one of our projects:

      req.on('socket', (socket) => {
          socket.setTimeout(timeout);
          socket.on('timeout', () => {
            req.abort();
            reject({....});
          });
        });

Can a similar logic be implemented in Axios (perhaps with a new socketTimeout config parameter)? I believe this would fix the issue reported by the original poster. For more info: https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback

@hiranya911
Copy link

It looks like req.setTimeout() will also achieve the same result: https://nodejs.org/api/http.html#http_request_settimeout_timeout_callback

Shall I put a small PR together to implement this fix? This will enable wider usage of Axios in our server-side projects.

@keyworks
Copy link

keyworks commented Aug 9, 2019

In case it's useful to anyone I've been patching the behaviour of timeout by using an interceptor. It uses a cancel token to cancel the request after timeout has elapsed.

const requestTimeoutInterceptor = config => {
  if (config.timeout === undefined || config.timeout === 0) {
    return config;
  }

  const source = axios.CancelToken.source();

  setTimeout(() => {
    source.cancel(`Cancelled request. Took longer than ${config.timeout}ms to get complete response.`);
  }, config.timeout);

  // If caller configures cancelToken, preserve cancelToken behaviour.
  if (config.cancelToken) {
    config.cancelToken.promise.then(cancel => {
      source.cancel(cancel.message);
    });
  }

  return { ...config, cancelToken: source.token };
};


axios.interceptors.request.use(requestTimeoutInterceptor);

@jasonsaayman jasonsaayman added this to To do in v1.0.0 via automation May 14, 2021
@jasonsaayman jasonsaayman changed the title Response timeout? Response timeout does not work as expected May 14, 2021
@jasonsaayman
Copy link
Member

See: #4209

@cgarrovillo
Copy link

I wanna clarify the behaviour of the timeout. Does timeout resolve or reject the promise? I just got a request that timed out and yet my .then() got called

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
0.19.0
  
In progress
v1.0.0
  
To do
Development

No branches or pull requests