【问题标题】:Retry failed requests with $http interceptor使用 $http 拦截器重试失败的请求
【发布时间】:2015-12-11 20:56:40
【问题描述】:

我的 webapp 正在与之通信的 API 有时会过载,如果它无法处理请求,则会发送 500 Internal Server Error。

我的 Web 应用程序可以发送 100 多个不同的请求,因此如果我对每个请求单独实施重试,我将花费数小时的打字时间。

我已经在使用 $httpProvider 拦截器了,这里是(简体)

$httpProvider.interceptors.push(function ($q) {
    return {
        responseError: function (response) {
            switch (response.status) {
                case 401 :
                    window.location = "/";
                    alert('Session has expired. Redirecting to login page');
                    break;
                case 500 :
                    // TODO: retry the request
                    break;
            }
            return $q.reject(response);
        }
    };
});

从服务器收到 500 响应码后如何重新发送请求?

【问题讨论】:

  • 也可以处理丢失的互联网连接(HTTP 0 状态)。你想让我在答案中添加它吗?
  • 如果您对答案不满意,您能告诉我们如何帮助您解决问题吗?如果它已经在答案的帮助下解决了,那么适当地处理赏金会很好,或者如果你自己解决了它,那么提供你的解决方案会很好。谢谢。

标签: angularjs angular-http-interceptors


【解决方案1】:

我还想在我的 response 块中重试请求,因此结合来自 SO 不同帖子的多个答案,我编写了如下拦截器 -

app.config(['$httpProvider', function ($httpProvider) {
    $httpProvider.interceptors.push(['$rootScope', '$cookies', '$q', '$injector', function ($rootScope, $cookies, $q, $injector) {
        var retries = 0, maxRetries = 3;

        return {
            request: function (config) {
                var csrf = $cookies.get("CSRF-Token");
                config.headers['X-CSRF-Token'] = csrf;
                if (config.data) config.data['CSRF-Token'] = csrf;
                return config;
            },
            response: function (r) {
                if (r.data.rCode == "000") {
                    $rootScope.serviceError = true;
                    if (retries < maxRetries) {
                        retries++;
                        var $http = $injector.get('$http');
                        return $http(r.config);
                    } else {
                        console.log('The remote server seems to be busy at the moment. Please try again in 5 minutes');
                    }
                }
                return r;
            },
            responseError: function (r) {
                if (r.status === 500) {
                    if (retries < maxRetries) {
                        retries++;
                        var $http = $injector.get('$http');
                        return $http(r.config);
                    } else {
                        console.log('The remote server seems to be busy at the moment. Please try again in 5 minutes');
                    }
                }

                retries = 0;
                return $q.reject(r);
            }
        }
    }]);
}])

致谢@S.Klechkovski、@Cameron 和https://stackoverflow.com/a/20915196/5729813

【讨论】:

    【解决方案2】:

    Angular 提供对配置对象的引用,$http 服务使用该对象在响应中执行请求(response.config)。这意味着如果我们可以在拦截器中注入 $http 服务,我们可以轻松地重新发送请求。由于循环依赖,无法在拦截器中简单注入 $http 服务,但幸运的是,有一种解决方法。

    这是一个如何实现此类拦截器的示例。

    $httpProvider.interceptors.push(function ($q, $injector) {
        var incrementalTimeout = 1000;
    
        function retryRequest (httpConfig) {
            var $timeout = $injector.get('$timeout');
            var thisTimeout = incrementalTimeout;
            incrementalTimeout *= 2;
            return $timeout(function() {
                var $http = $injector.get('$http');
                return $http(httpConfig);
            }, thisTimeout);
        };
    
        return {
            responseError: function (response) {
                if (response.status === 500) {
                    if (incrementalTimeout < 5000) {
                        return retryRequest(response.config);
                    }
                    else {
                        alert('The remote server seems to be busy at the moment. Please try again in 5 minutes');
                    }
                }
                else {
                    incrementalTimeout = 1000;
                }
                return $q.reject(response);
            }
        };
    });
    

    注意:在此示例实现中,拦截器将重试请求,直到您收到状态不同于 500 的响应。对此的改进可以是在重试和仅重试一次之前添加一些超时。

    【讨论】:

    • 你能提供更多关于失败原因的信息吗?
    • 请求失败的原因是服务器过载。如果队列太大或内存不足,服务器返回 504 响应
    • 恕我直言,您应该在服务器端解决此问题。如果由于某些原因你不能这样做,最好你可以在重试之前添加超时,并希望在下一次请求时服务器可用。
    • 这与我们为 $http 服务所做的相同。 var $timeout = $injector.get('$timeout');
    • 非常有用!在我的情况下,我使用response.status === -1 检查取消/超时请求(请参阅this post
    【解决方案3】:

    您可以通过进一步扩展状态代码检查来检查任何可能的服务器端错误。此拦截器将尝试多次重试请求,并将在任何响应代码为 500 或更高的情况下这样做。重试前会等待 1 秒,尝试 3 次后放弃。

    $httpProvider.interceptors.push(function ($q, $injector) {
    
        var retries = 0,
            waitBetweenErrors = 1000,
            maxRetries = 3;
    
        function onResponseError(httpConfig) {
            var $http = $injector.get('$http');
            setTimeout(function () {
                return $http(httpConfig);
            }, waitBetweenErrors);
        }
    
        return {
            responseError: function (response) {
                if (response.status >= 500 && retries < maxRetries) {
                    retries++;
                    return onResponseError(response.config);
                }
                retries = 0;
                return $q.reject(response);
            }
        };
    });
    

    【讨论】:

    • 我是 Angular 新手,但在我看来,这里应该使用 $timeout 而不是 setTimeout,以便将新创建的 $http 承诺的状态传递给失败的$httppromise .换句话说,创建 500 请求的 Promise 继续存在,如果重试成功,应该触发 success 回调。
    • @Dan 你说得对...setTimeout 吞下一个响应错误,在所有重试失败后,原始请求的成功函数被调用,并带有未定义的数据。管道状态传播successerror
    • 另外,如果我没记错的话,每个请求都会共享重试次数,所以如果多个请求同时失败,此解决方案将不起作用。
    • 您应该使用 var retryCounter = [] 并将 url 添加为 key + count 作为 value。之后取消设置。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多