Обработка ответа HTTP 302 от прокси в angularjs

У меня есть обратный прокси, который проверяет глобальную аутентификацию для нескольких приложений. Когда пользователь отключен, но все еще пытается использовать мое приложение, прокси отправляет ответ 302:

HTTP/1.1 302 Found
Date: Wed, 11 Sep 2013 09:05:34 GMT
Cache-Control: no-store
Location: https://other.url.com/globalLoginPage.html
Content-Length: 561
Content-Type: text/html; charset=iso-8859-1
Via: 1.1 my-proxy.com
Connection: Keep-Alive

В angularJs обратный вызов ошибки вызывается, но заголовки ответа пусты, статус равен 0, а данные - это пустая строка. Кажется, что я действительно ничего не могу сделать, чтобы обработать ответ ...
Я видел несколько вопросов по этому поводу, но до сих пор не понимаю, что происходит (CORS из-за прокси-сервера или домен в местоположении ?, 302 поведение браузера?).
В частности, есть эта часть из ответа (https://stackoverflow.com/a/17903447/1093925):

Примечание. Если ваш сервер устанавливает код ответа 301 или 302, вы не сможете получить заголовок Location, так как за ним автоматически и прозрачно будет следовать объект XMLHttpRequest.

А как насчет этого объекта XMLHttpRequest?
В очень старой версии Chrome (не может использовать более новую версию) я вижу, что соответствующий запрос на сетевой панели, но кажется, что он не работает, поскольку нет ответа.
В последней версии firefox ничего не происходит.

Могу ли я что-нибудь с этим сделать, так как я не могу изменить конфигурацию прокси и ответ?

Обновление:
Я воспроизвел свой сценарий сегодня, и благодаря более новой версии firebug я смог получить более подробную информацию о том, что происходит.
Я был недалеко от anwser в моем вопросе: Междоменная политика.
Поскольку это HTTP-запрос, сделанный моим приложением, браузер отклоняет следующий XMLHttpRequest (который в приложении выглядит как тот же запрос). Отсюда ошибка и пустой ответ.
Думаю, ничего особенного я не могу с этим поделать


person D0m3    schedule 11.09.2013    source источник
comment
Кто-нибудь знает, как получить заголовок Location в AngularJS с 302? Он отображается в браузере, но при проверке ответа об ошибке (конфигурация, заголовки, данные и т. Д.) Он, похоже, не присутствует в приложении Angular .... Мне нужно перенаправить моего пользователя на любой URL-адрес, указанный в Заголовок местоположения   -  person jlewkovich    schedule 07.07.2016


Ответы (5)


У меня была такая же проблема в моем приложении. Вы не можете действительно "поймать" ответ перенаправления 302. Браузер поймает его до того, как Angular поймает его. так что на самом деле, когда вы все же получите ответ - уже слишком поздно.

Плохая новость: это не проблема для платформы angular. XmlHttpRequest не поддерживает такое поведение, и браузер действует до того, как вы сможете что-либо с этим сделать. ссылка: https://stackoverflow.com/questions/228225/prevent-redirection-of-xmlhttprequest

Хорошие новости: есть много способов обойти эту проблему, перехватив ответ и найти способ распознать, что это ваш любимый 302. Это хитрость, но это лучшее, что вы можете сделать в момент.

Итак. Например, в моем приложении перенаправление вернулось на страницу login.html приложения, а в приложении я получил ответ со статусом 200, а данные ответа были содержимое моей страницы login.html. поэтому в моем перехватчике я проверил, является ли результат строкой (обычно нет! Так что нет проблем с эффективностью ..), и если да, то проверил, является ли это моей страницей login.html. Таким образом, я мог поймать перенаправление и обработать его по-своему.

yourApp.factory('redirectInterceptor', ['$location', '$q', function($location, $q) {
    return function(promise) {
        promise.then(
            function(response) {
                if (typeof response.data === 'string') {
                    if (response.data.indexOf instanceof Function &&
                        response.data.indexOf('<html id="ng-app" ng-app="loginApp">') != -1) {
                        $location.path("/logout");
                        window.location = url + "logout"; // just in case
                    }
                }
                return response;
            },
            function(response) {
                return $q.reject(response);
            }
        );
        return promise;
    };
}]);

Затем вставьте этот перехватчик в свое приложение. что-то вроде этого:

$httpProvider.responseInterceptors.push('redirectInterceptor');

удачи.

person Ofer Segev    schedule 16.07.2014
comment
Мне очень нравится этот подход, я использовал его, чтобы захватить заголовок страницы входа, response.data.indexOf ('‹title› title to grab ‹/title›')! = -1. Но не вышло! какие идеи почему? - person ssayyed; 17.07.2014
comment
Кстати, мне не нравится этот подход (то есть подход моего ответа), но в этом случае я думаю, что у нас нет особого выбора ... в любом случае - я понятия не имею, почему он не работает для вас. Возможно, данные, над которыми вы работаете, не содержат вашего HTML-содержимого. попробуйте остановиться внутри этого кода с помощью отладчика; и console.log () response.data, чтобы выяснить, почему это с вами происходит. - person Ofer Segev; 20.07.2014
comment
Спасибо за ваш ответ, на самом деле мои данные response.data пусты. Думаю, я только что понял проблему благодаря более новой версии firebug. Смотрите мой ответ. - person D0m3; 11.09.2014
comment
хороший ответ, действительно помогает мне разобраться в поведении 302 в браузерах. - person nightire; 10.07.2015
comment
@OferSegev, ваше решение является правильным только в том случае, если перенаправление связано с URL-адресом в том же домене. - person john Smith; 11.10.2015
comment
Да, @johnSmith. Я ищу решение для перехвата ответов с переадресацией от перекрестного домена. - person Rahul Patil; 21.12.2015
comment
Хороший обходной путь, но не работает при интеграции сторонних библиотек. Например, oidc-client, который ожидает много перенаправлений, но вместо этого получает 200 и кешированный ответ от сервис-воркера. - person JustAMartin; 05.05.2020

Ваш 302 -Redirect обрабатывается непосредственно браузером, и вы ничего не можете с этим поделать напрямую. Однако вы можете использовать httpInterceptor, чтобы помочь вам. Вам нужно будет включить $httpProvider в список DI вашего приложения, а затем где-нибудь в вашей функции конфигурации поместить ссылку на него следующим образом:

$httpProvider.responseInterceptors.push('HttpInterceptor');

Примерный перехватчик выглядит так:

window.angular.module('HttpInterceptor', [])
.factory('HttpInterceptor', ['$q', '$injector',
    function($q, $injector) {
        'use strict';

        return function(promise) {
            return promise.then(success, error);
        };

        function success(response) {
            return response;
        }

        function error(response) {
            var isAuthRequest = (response.config.url.indexOf('/v1/rest/auth') !== -1);

            //if we are on the authenticating don't open the redirect dialog
            if (isAuthRequest) {
                return $q.reject(response);
            }

            //open dialog and return rejected promise
            openErrorDialog(response);
            return $q.reject(response);
        }

        function openErrorDialog(response) {
            $injector.get('$dialog').dialog({
                backdropFade: true,
                dialogFade: true,
                dialogClass: 'modal newCustomerModal',
                resolve: {
                    errorData: function() {
                        return response.data;
                    },
                    errorStatus: function() {
                        return response.status;
                    }
                }
            })
            .open('/views/error-dialog-partial.htm',
                'errorDialogController')
            .then(function(response) {
                if (response) {
                    window.location = '/';
                }
            });
        }
    }
]);
person MBielski    schedule 31.12.2013
comment
Спасибо за ответ, но я не понимаю, как это помогает в моей проблеме. В перехватчике у вас по-прежнему нет информации об ответе. - person D0m3; 06.01.2014
comment
Хм ... ты прав. Вы пробовали обрабатывать изменение маршрута через $ route? (docs.angularjs.org/api/ngRoute.$route) - person MBielski; 06.01.2014
comment
На самом деле я делаю REST-запрос, поэтому считаю, что нет никакой связи с $ route. - person D0m3; 07.01.2014
comment
Да, но разве ваш маршрут не меняется из-за 302-й? Вы можете обнаружить это изменение и отреагировать на него либо в $ routeChangeStart, либо в $ routeChangeSuccess. - person MBielski; 07.01.2014
comment
Нет, пользователь не перенаправлен. Я делаю HTTP-запрос (с $ http) в angularjs и получаю ответ как ошибку. Больше ничего не происходит. - person D0m3; 08.01.2014
comment
Хорошо, это странно. Коды состояния 300-й серии не являются ошибками, и ни браузер, ни Angular не должны реагировать на них таким образом, но, видимо, именно это и происходит. Что касается ошибок, я все еще думаю, что вам нужен перехватчик. Ты пробовал это? - person MBielski; 09.01.2014
comment
Хорошо, я должен сказать, что не знаю, что делать дальше. Извините, я не смог помочь. - person MBielski; 09.01.2014
comment
В любом случае спасибо за попытку. Я проведу небольшое исследование, чтобы увидеть, нормально ли, что angularjs рассматривает ответ 302 как ошибку. - person D0m3; 09.01.2014
comment
Я могу вам сказать, что мое приложение так не реагирует, fwiw. - person MBielski; 10.01.2014
comment
Я только что понял, почему у меня ошибка. См. Мой ответ. - person D0m3; 11.09.2014

У меня была очень похожая проблема, и я рассмотрел решение, предоставленное Ofer Segev, но проверка содержимого ответа, чтобы увидеть, соответствует ли он фрагменту html другой страницы, мне показалось слишком взломанным. Что происходит, когда кто-то меняет эту страницу?

К счастью, у меня был контроль и над серверной частью, поэтому вместо возврата 302 (перенаправление) я вернул 403 (запрещено) и передал желаемое место в заголовках. В отличие от 302, 403 будет обрабатываться вашим обработчиком ошибок, где вы можете решить, что делать дальше. Вот и получился мой обработчик:

function ($scope, $http) {
    $http.get(_localAPIURL).then(function (response) {
            // do what I'd normally do
        }, function (response) {
        if (response.status == 403)
        {
            window.location.href = response.headers().location;
        }
        else
        {
            // handle the error
        }
person plunkg    schedule 20.09.2016
comment
Более удобное решение, если у вас есть доступ к серверной части. - person ded; 16.05.2017
comment
У меня была такая же мысль, но я хочу вернуть только 401, если это вызов веб-API. Этот вопрос / ответ SO касается проблемы. - person Weihui Guo; 31.05.2018

Как обсуждалось выше, ответ 302 недоступен для Angular, поскольку xmlHttpRequest не поддерживает возвращаемые перенаправления; браузер действует до того, как вы сможете что-либо с этим поделать.

Помимо обработки пользовательских ответов с данными и переопределения кодов состояния,
вы можете использовать более общее решение и добавить настраиваемый заголовок перенаправления, например X-Redirect, и действовать в соответствии с этим. Значение X-Redirect должно быть URL-адресом, на который вы хотите перенаправить, например, https://other.url.com/globalLoginPage.html

$http.get(orig_url).then(function(response) {
        // do stuff
    }, function (response) {
        var headers = response.headers();
        if ('x-redirect' in headers)
        {
            document.location.href = headers['x-redirect'];
        }
        return response;
    }
}

Для более глобального решения вы также можете использовать HTTP-перехватчик.

app.factory('myHttpInterceptor', function ($q, myCache) {
    return {
        request: function (config) {
            return config;
        },
        response: function(response){
            var headers = response.headers();
            if ('x-redirect' in headers)
            {
                document.location.href = headers['x-redirect'];
            }    
            return response;
        },
        responseError: function(rejection) {    
            switch(rejection.status)
            {
                case 302:
                    // this will not occur, use the custom header X-Redirect instead
                    break;
                case 403:
                    alert.error(rejection.data, 'Forbidden');
                    break;
            }
            return $q.reject(rejection);
        }
    };
});

Вы можете установить пользовательский заголовок на стороне сервера.
Например, если вы используете PHP Symfony:

$response = new Response('', 204);
$response->headers->set('X-Redirect', $this->generateUrl('other_url'));
return $response;
person browniebytes    schedule 05.12.2018

Просто для справки на случай, если кто-то все еще смотрит на это: вы также можете выполнить вызов, который, как ожидается, вернет форму 302 в iframe. Таким образом вы останетесь на странице и сможете общаться с https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

person Remco Vlierman    schedule 20.05.2020