For the purposes of this, I'm going to pretend the original url is http://host/form
and the new url is https://host/form
. (Note that before I ship this, both URLs are going to be secure. However, the nonsecure-to-secure seems like a convenient redirect to test this on.)
I'm accessing a web API using NSURLConnection
that redirects me. Basically, I want to take everything I just submitted to http://hostaform
and re-submit it to https://host/form
. I thought this would be the default behavior, but it looks like the body is being lost in the redirect.
So I think I need to handle the connection:willSendRequest:redirectResponse:
event of the NSURLConnection
's delegate and re-attach the body. The problem is this message seems woefully underdocumented. The only info I can find on this method is NSURLConnection Class Reference, which isn't very helpful. Among other things, it includes this:
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.
I'm not sure what this means. Combined with an initial willSendRequest:
invocation, I think this is means willSendRequest:
is being sent even for my initial request, prior to the redirect response. Is that correct?
So I've added code to my delegate to retain the body an extra time, and added this willSendRequest:
handler:
- (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;
}
}
It doesn't work. But I'm not even sure if this is the right approach. It seems excessively hackish to me. What should I be doing? Is this documented anywhere? I've found nothing useful in Apple's documentation or using Google so far.
(This is on the iPhone, although there doesn't seem to be much difference in these classes.)
There's a note in section 10.3.2 of RFC 2616 about this behaviour:
Note: When automatically redirecting a POST request after
receiving a 301 status code, some existing HTTP/1.0 user agents
will erroneously change it into a GET request.
So this behaviour seems to be non-standard but historical. That GET
request is not a POST
, and it'll be missing the payload.
Interestingly enough, this is also in the same section:
If the 301 status code is received in response to a request other
than GET or HEAD, the user agent MUST NOT automatically redirect the
request unless it can be confirmed by the user, since this might
change the conditions under which the request was issued.
That's pretty clear and seems to indicate we can't fix this, but I think ignoring this for the purpose of our own web service clients for services we pick (or control) is probably the least bad alternative.
So how do we solve this?
Instead of the willSendResponse:
in the original question, I'm using this:
- (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;
}
}
The idea here is that instead of cloning the new request and trying to shape it the same as the one Cocoa Touch sends me, I create a clone of the original request and change just the URL to match the request Cocoa Touch sent me. That original request is still a POST
with the payload attached.
If you control the server, it's worth reading RFC 2616, section 10.3 in its entirety to see if there's a better code you can use (while checking, of course, that iOS handles the better code as it should).
You could also make a mutable copy of the redirected request and replace its HTTP method with the HTTP method of the original request. Same general principle, though that would favour keeping things from the new request rather than the old. In some circumstances that might work better, but I haven't tested this yet.
You should be checking the HTTP response status code sent by the server to determine whether to send a GET or repeat the POST. For 303 (or 302), send a GET request. For 307, repeat the POST.
i had the same problem with redirecting.
Thanks to AJSoaks!
I tried as he suggested and the problem is resolved.
So, i was trying to post the username and password through the POST method, and i saw that server redirected my request. As AJSoaks says, in case if there is 302 error you should repeat the request but this time using GET method instead of previous POST.
... at some point you have the following lines:
... it can be inside if your IBAction (button pressed) method or wherever you want...
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];
Than you should also implement the redirect NSURLConnection delegate method, with the following signature:
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
inside of this method, in case if you have SERVER's Error 302 or 303 you should implement something similar to the code bellow, just copy the code that you see and replace it with the new URL (redirected). The new URL you can see in the browser or if you want it is very useful, also in the future, checking it with Firebug (Firefox plugin) or Safari WEB INSPECTOR. If you use Firebug all information you can find under the "Net" option:
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;
NSURLConnection does not add the originalRequest headers into the redirected request in the "willSendRequest: (NSURLRequest *)inRequest".
You can workaround this problem by adding "originalRequest.headers" into the redirected request.