Хобрук: Ваш путь к мастерству в программировании

Правильная обработка перенаправлений с помощью NSURLConnection

Для этого я собираюсь представить, что исходный URL-адрес http://host/form, а новый URL-адрес https://host/form. (Обратите внимание, что до того, как я отправлю это, оба URL-адреса будут безопасными. Однако переход от незащищенного к безопасному кажется удобным перенаправлением для проверки.)

Я получаю доступ к веб-API, используя NSURLConnection, который меня перенаправляет. По сути, я хочу взять все, что я только что отправил в http://hostaform, и повторно отправить это в https://host/form. Я думал, что это будет поведение по умолчанию, но похоже, что тело теряется при перенаправлении.

Поэтому я думаю, что мне нужно обработать событие connection:willSendRequest:redirectResponse: делегата NSURLConnection и повторно прикрепить тело. Проблема в том, что это сообщение выглядит крайне недокументированным. Единственная информация, которую я могу найти об этом методе, - это Ссылка на класс NSURLConnection, которая не очень полезна. Среди прочего, в него входит:

redirectResponse: The URL response that caused the redirect. May be nil in cases where this method is not being sent as a result of involving the delegate in redirect processing.

Я не понимаю, что это значит. В сочетании с первоначальным вызовом willSendRequest:, я думаю, это означает, что willSendRequest: отправляется даже для моего первоначального запроса до ответа перенаправления. Это правильно?

Итак, я добавил код своему делегату, чтобы сохранить тело еще раз, и добавил этот willSendRequest: обработчик:

- (NSURLRequest *)connection: (NSURLConnection *)inConnection
             willSendRequest: (NSURLRequest *)inRequest
            redirectResponse: (NSURLResponse *)inRedirectResponse;
{
    if (inRedirectResponse) {
        NSMutableURLRequest *r = [[inRequest mutableCopy] autorelease];
        [r setURL: [inRedirectResponse URL]];
        [r setHTTPBody: body];
        return r;
    } else {
        return inRequest;
    }
}

Не работает. Но я даже не уверен, что это правильный подход. Мне это кажется излишне хакерским. Что мне делать? Это где-нибудь задокументировано? Я пока не нашел ничего полезного ни в документации Apple, ни в использовании Google.

(Это на iPhone, хотя, похоже, нет большой разницы в этих классах.)


Ответы:


1

В разделе 10.3.2 RFC 2616 есть примечание об этом поведении:

Примечание. При автоматическом перенаправлении запроса POST после получения кода состояния 301 некоторые существующие пользовательские агенты HTTP / 1.0 ошибочно изменят его на запрос GET.

Так что такое поведение кажется нестандартным, но историческим. Этот GET запрос не является POST, и в нем будут отсутствовать полезные данные.

Что интересно, это тоже в том же разделе:

Если код состояния 301 получен в ответ на запрос, отличный от GET или HEAD, пользовательский агент НЕ ДОЛЖЕН автоматически перенаправлять запрос, если он не может быть подтвержден пользователем, поскольку это может изменить условия, при которых запрос был выпущен.

Это довольно ясно и, кажется, указывает на то, что мы не можем это исправить, но я думаю, что игнорирование этого для целей наших собственных клиентов веб-сервисов для сервисов, которые мы выбираем (или контролируем), вероятно, является наименее плохой альтернативой.

Так как же решить эту проблему?

Вместо willSendResponse: в исходном вопросе я использую это:

- (NSURLRequest *)connection: (NSURLConnection *)connection
             willSendRequest: (NSURLRequest *)request
            redirectResponse: (NSURLResponse *)redirectResponse;
{
    if (redirectResponse) {
        // we don't use the new request built for us, except for the URL
        NSURL *newURL = [request URL];
        // Previously, store the original request in _originalRequest.
        // We rely on that here!
        NSMutableURLRequest *newRequest = [_originalRequest mutableCopy];
        [newRequest setURL: newURL];
        return newRequest;
    } else {
        return request;
    }
}

Идея здесь в том, что вместо клонирования нового запроса и попытки сформировать его так же, как тот, который мне отправляет Cocoa Touch, я создаю клон исходного запроса и меняю только URL-адрес, чтобы он соответствовал запросу, который Cocoa Touch отправил мне. Этот исходный запрос по-прежнему POST с приложенной полезной нагрузкой.

Если вы управляете сервером, стоит прочитать полностью RFC 2616, раздел 10.3 чтобы увидеть, есть ли лучший код, который вы можете использовать (при проверке, конечно, что iOS обрабатывает лучший код, как и должно).

Вы также можете сделать изменяемую копию перенаправленного запроса и заменить его HTTP-метод на HTTP-метод исходного запроса. Тот же самый общий принцип, хотя это позволит сохранить элементы нового запроса, а не старого. В некоторых случаях это может сработать лучше, но я еще не тестировал это.

18.09.2009
  • Вы сказали Вместо willSendResponse :, но в URLConnection такого нет. Ваш ответ похож на тот, который у вас был выше, за исключением того, что вы отправляете URL-адрес оригиналу? И вы ссылаетесь на запрос в своем mutableCopy, который нигде не объявлен. Ответ очень запутанный. 24.01.2012
  • willSendResponse является частью протокола NSURLConnectionDataDelegate. mutableCopy - это метод, встроенный в NSURLRequest. И что я делаю, так это отбрасываю новый запрос, созданный NSURLConnection, и повторно использую свой исходный запрос (скопированный, с измененным только URL-адресом). 25.01.2012
  • Что ж, это был бессмысленно потраченный день. Ура за решение, это спасло мое рассудок :) 28.02.2012
  • Я не думаю, что есть версия для синхронных подключений? 29.10.2013
  • Что ж, настоящий ответ на этот вопрос - никогда не использовать синхронные соединения. Никогда. Даже если вы думаете, что у вас есть веская причина, этого недостаточно, чтобы ваше приложение вылетало из-за нее. И он рухнет. 30.10.2013
  • Я хотел указать, что при копировании исходного запроса копируются исходные заголовки, включая заголовок авторизации. Для меня мой исходный запрос требовал базовой аутентификации, но запрос перенаправления не требовал никакой аутентификации, поэтому мне пришлось быть немного более разумным при копировании исходного запроса. Результатом была ошибка 400 уровней. 09.01.2014
  • Я просмотрел параметр запроса в iOS 8.1, и метод HTTP действительно изменился на GET. Интересно, почему Apple реализовала такое ошибочное поведение. В любом случае, хорошо об этом знать. Спасибо! 02.03.2015
  • Я предполагаю, что это восходит к какой-то NSDate в истории, если вы понимаете, о чем я. :) Эта заметка вполне могла быть написана о NextStep, а поведение осталось неизменным вплоть до iOS. 03.03.2015
  • @TPoschel: Добавил примечание об этом к ответу. Мне придется попробовать это несколько раз, прежде чем я порекомендую, но, похоже, это нормально. :) 03.03.2015
  • Для этого есть очень веская причина. Многие веб-сайты отвечают на POST, перенаправляя URL-адреса в другое место, и вы не захотите повторять POST на этот новый URL-адрес, потому что вы будете загружать данные на страницу / скрипт, для которых это не важно. В редких случаях, когда вам нужно перенаправить фактический запрос POST, используйте код 307. 21.05.2015
  • Согласно RFC, такое поведение противоречит спецификации. 25.05.2015

  • 2

    Вы должны проверять код состояния HTTP-ответа, отправленный сервером, чтобы определить, отправлять ли GET или повторять POST. Для 303 (или 302) отправьте запрос GET. Для 307 повторите POST.

    03.08.2010
  • Идеально для технической корректности. Это правильно делать в веб-браузере и правильно делать теоретически, но неправильно делать указание на веб-службу, написанную на PHP. Вы, вероятно, входите в службу, а переход к GET означает, что вы теряете полезную нагрузку, которая будет выполнять вход. 07.10.2011
  • Прочтите новый RFC. Вы более правы, чем я думал; если вы можете управлять сервером, переход на 307 - лучший подход. 30.01.2013

  • 3

    у меня была такая же проблема с перенаправлением. Спасибо AJSoaks! Я попробовал, как он предложил, и проблема решена.

    Итак, я пытался опубликовать имя пользователя и пароль с помощью метода POST, и я увидел, что сервер перенаправил мой запрос. Как говорит AJSoaks, в случае ошибки 302 вы должны повторить запрос, но на этот раз с использованием метода GET вместо предыдущего POST.

    ... в какой-то момент у вас есть следующие строки: ... он может быть внутри вашего метода IBAction (кнопка нажата) или где угодно ...

    NSMutableString *postString = [[NSMutableString alloc] init];
    
    [postString appendString:@"username=YourUsername&password=YourPassword"];
    
        //the original URL (https means that it supports SSL protocol)
        //it doesn't change anything, don't worry about it
    NSURL *URL = [NSURL URLWithString:@"https://loginWebpageURL"];
    
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
    
    [request setHTTPMethod:@"POST"];    
    [request setValue:[NSString stringWithFormat:@"%d", [postString length]] forHTTPHeaderField:@"Content-length"];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-type"];
    [request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
    
    [NSURLConnection connectionWithRequest:request delegate:self];
    
    [postString release];
    [request release];
    

    Затем вы должны также реализовать метод делегата перенаправления NSURLConnection со следующей подписью:

    - (NSURLRequest *)connection:(NSURLConnection *)connection
                willSendRequest:(NSURLRequest *)request
               redirectResponse:(NSURLResponse *)redirectResponse
    

    внутри этого метода, если у вас есть ошибка СЕРВЕРА 302 или 303, вы должны реализовать что-то похожее на приведенный ниже код, просто скопируйте код, который вы видите, и замените его новым URL-адресом (перенаправленным). Новый URL-адрес, который вы можете увидеть в браузере или, если хотите, очень полезен в будущем, проверяя его с помощью Firebug (плагин Firefox) или Safari WEB INSPECTOR. Если вы используете Firebug, всю информацию вы можете найти в разделе «Сеть»:

    if (redirectResponse) {
    
        NSLog(@"REDIRECT");
        NSMutableURLRequest *requestTmp = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://areaclienti.tre.it/selfcare/areaclienti133/4552_infoCosti_ITA_HTML.xsl"]];
    
        return [requestTmp autorelease];
    }
    
    //return original request in case thay there is no redirecting...
    else return request;
    
    06.03.2011
  • Спасибо, ваши коды дали мне полное объяснение этой проблемы, большое спасибо. Но у меня есть некоторые предложения: вновь созданный requestTmp запрос должен быть назначен с помощью метода HTTP, GET, например: [requestTmp setHTTPMethod:@"GET"] 27.02.2015

  • 4

    NSURLConnection не добавляет заголовки originalRequest в перенаправленный запрос в «willSendRequest: (NSURLRequest *) inRequest».

    Вы можете обойти эту проблему, добавив «originalRequest.headers» в перенаправленный запрос.

    01.04.2014
    Новые материалы

    Основы принципов S.O.L.I.D, Javascript, Git и NoSQL
    каковы принципы S.O.L.I.D? Принципы SOLID призваны помочь разработчикам создавать надежные, удобные в сопровождении приложения. мы видим пять ключевых принципов. Принципы SOLID были разработаны..

    Как настроить Selenium в проекте Angular
    Угловой | Селен Как настроить Selenium в проекте Angular Держите свое приложение Angular и тесты Selenium в одной рабочей области и запускайте их с помощью Mocha. В этой статье мы..

    Аргументы прогрессивного улучшения почти всегда упускают суть
    В наши дни в кругах веб-разработчиков много болтают о Progressive Enhancement — PE, но на самом деле почти все аргументы с обеих сторон упускают самую фундаментальную причину, по которой PE..

    Введение в Джанго Фреймворк
    Схема «работать умно, а не усердно» В этой и последующих статьях я познакомлю вас с тем, что такое фреймворк Django и как создать свое первое приложение с помощью простых и понятных шагов, а..

    Настольный ПК как «одно кольцо, чтобы править всеми» домашних компьютеров
    Вид после 9 месяцев использования С настольных компьютеров все началось, но в какой-то момент они стали «серверами», и мы все перешли на ноутбуки. В прошлом году я столкнулся с идеей настольных..

    Расширенные методы безопасности для VueJS: реализация аутентификации без пароля
    Руководство, которое поможет вам создавать безопасные приложения в долгосрочной перспективе Безопасность приложений часто упускается из виду в процессе разработки, потому что основная..

    стройный-i18следующий
    Представляем стройную оболочку для i18next. Эта библиотека, основанная на i18next, заключает экземпляр i18next в хранилище svelte и отслеживает события i18next, такие как languageChanged,..