it-swarm.com.ru

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

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

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

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

redirectResponse: URL-ответ, вызвавший перенаправление. Может быть ноль в случаях, когда этот метод не отправляется в результате вовлечения делегата в обработку перенаправления.

Я не уверен, что это значит. В сочетании с первоначальным вызовом 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, хотя в этих классах нет особой разницы.)

25
Steven Fisher

Есть примечание в разделе 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 исходного запроса. Тот же самый общий принцип, хотя это будет способствовать сохранению вещей из нового запроса, а не из старого. В некоторых случаях это может работать лучше, но я еще не проверял это.

38
Steven Fisher

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

13
AJSoaks

у меня была такая же проблема с перенаправлением. Спасибо 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

внутри этого метода, в случае, если у вас есть ошибки SERVER 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;
4
klusinyan

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

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

0
user2088250