web-dev-qa-db-fra.com

Gestion correcte des redirections avec NSURLConnection

Pour cela, je vais prétendre que l'URL d'origine est http://Host/form et la nouvelle URL est https://Host/form. (Notez qu'avant d'envoyer cette copie, les deux URL seront sécurisées. Toutefois, le non sécurisé à sécurisé semble être une redirection commode pour le tester.)

J'accède à une API Web à l'aide de NSURLConnection qui me redirige. Fondamentalement, je veux prendre tout ce que je viens de soumettre à http://hostaform et le soumettre à nouveau à https://Host/form. Je pensais que ce serait le comportement par défaut, mais il semblerait que le corps soit perdu dans la redirection.

Je pense donc que je dois gérer l'événement connection:willSendRequest:redirectResponse: du délégué de la NSURLConnection et attacher à nouveau le corps. Le problème est que ce message semble terriblement sous-documenté. La seule information que je peux trouver sur cette méthode est NSURLConnection Class Reference , ce qui n’est pas très utile. Entre autres choses, cela inclut:

redirectResponse: La réponse de l'URL à l'origine de la redirection. Peut être nul dans les cas où cette méthode n'est pas envoyée en raison de l'implication du délégué dans le traitement de la redirection.

Je ne suis pas sûr de ce que cela signifie. Combiné à une invocation initiale de willSendRequest:, je pense que cela signifie que willSendRequest: est envoyé même pour ma demande initiale, avant la réponse de redirection. Est-ce exact?

J'ai donc ajouté du code à mon délégué pour conserver le corps une fois supplémentaire, et ajouté ce gestionnaire 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;
    }
}

Ça ne marche pas Mais je ne suis même pas sûr que ce soit la bonne approche. Cela me semble excessivement ridicule. Que devrais-je faire? Est-ce documenté quelque part? Jusqu'à présent, je n'ai trouvé aucun élément utile dans la documentation d'Apple ou sur Google.

(Ceci est sur l'iPhone, bien qu'il ne semble pas y avoir beaucoup de différence entre ces classes.)

25
Steven Fisher

Il y a une note dans la section 10.3.2 de la RFC 2616 à propos de ce comportement:

Remarque: lors de la redirection automatique d'une demande POST après que A reçu un code d'état 301, certains agents utilisateurs HTTP/1.0 existants Le transforment par erreur en une demande GET.

Donc, ce comportement semble être non standard mais historique. Cette demande GET n'est pas une POST et il manquera la charge utile.

Fait intéressant, cela se trouve également dans la même section:

Si le code d'état 301 est reçu en réponse à une demande autre que Que GET ou HEAD, l'agent d'utilisateur NE DOIT PAS automatiquement rediriger la demande À moins que l'utilisateur ne puisse le confirmer, car cela pourrait changer les conditions dans lesquelles la demande a été émise.

C'est assez clair et semble indiquer que nous ne pouvons pas résoudre ce problème, mais je pense que le fait de ne pas en tenir compte pour les besoins de nos propres clients de services Web pour les services que nous sélectionnons (ou contrôlons) est probablement la moins mauvaise alternative.

Alors, comment pouvons-nous résoudre ce problème?

Au lieu du willSendResponse: dans la question initiale, j'utilise ceci:

- (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;
    }
}

L'idée ici est qu'au lieu de cloner la nouvelle requête et d'essayer de la façonner de la même manière que celle que Cocoa Touch m'a envoyée, je crée un clone de la requête d'origine et ne modifie que l'URL pour qu'elle corresponde à la requête que Cocoa Touch m'a envoyée. Cette demande initiale est toujours une POST avec la charge utile attachée.

Si vous contrôlez le serveur, vous devriez lire RFC 2616, section 10.3 dans son intégralité pour voir s’il existe un meilleur code que vous pouvez utiliser (tout en vérifiant, bien sûr, que iOS gère le meilleur code comme il se doit).

Vous pouvez également créer une copie mutable de la requête redirigée et remplacer sa méthode HTTP par la méthode HTTP de la requête d'origine. Même principe général, bien que cela favoriserait les choses de la nouvelle demande plutôt que de l’ancienne. Dans certaines circonstances, cela pourrait fonctionner mieux, mais je ne l'ai pas encore testé.

38
Steven Fisher

Vous devez vérifier le code d'état de la réponse HTTP envoyé par le serveur pour déterminer s'il faut envoyer un message GET ou répéter le test POST. Pour 303 (ou 302), envoyez une demande GET. Pour 307, répétez le POST.

13
AJSoaks

j'ai eu le même problème avec la redirection. Merci à AJSoaks! J'ai essayé comme il l'avait suggéré et le problème est résolu.

J'essayais donc de publier le nom d'utilisateur et le mot de passe via la méthode POST, et j'ai vu ce serveur redirigé ma demande. Comme le dit AJSoaks, au cas où il y aurait une erreur 302, vous devriez répéter la demande mais cette fois-ci en utilisant la méthode GET au lieu du précédent POST.

... à un moment donné, vous avez les lignes suivantes: ... cela peut être à l'intérieur si votre méthode IBAction (bouton enfoncé) ou où vous voulez ...

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];

Ensuite, vous devez également implémenter la méthode de délégué NSURLConnection de redirection, avec la signature suivante:

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

dans cette méthode, si vous avez une erreur SERVEUR 302 ou 303, vous devez implémenter quelque chose de similaire au code ci-dessous, copiez simplement le code que vous voyez et remplacez-le par la nouvelle URL (redirigée). La nouvelle URL que vous pouvez voir dans le navigateur ou si vous le souhaitez est très utile, même à l'avenir, en la vérifiant avec Firebug (plugin Firefox) ou Safari WEB INSPECTOR. Si vous utilisez Firebug toutes les informations que vous pouvez trouver sous l’option "Net":

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 n'ajoute pas les en-têtes originalRequest dans la requête redirigée dans le "willSendRequest: (NSURLRequest *) inRequest" 

Vous pouvez contourner ce problème en ajoutant "originalRequest.headers" à la demande redirigée.

0
user2088250