xcode, nslog weird hitch - iphone

The code below gives me an NSLog redirect location on a tinyurl link when clicking on a tinyurl link. However, if I remove the following piece of code [[UIApplication sharedApplication] openURL:[request URL]];, it will give me the actual website in NSLog and not the tinyurl link. How can I make it where I can have the actual website URL in NSLog while the share application code is presented?
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
NSLog(#"Redirect Location: %#",[request.URL absoluteString]);
[[UIApplication sharedApplication] openURL:[request URL]];
}

Short answer:
Using UIWebViewDelegate method shouldStartLoadWithRequest is not a great mechanism for figuring out to which site you'll be redirected (especially since, judging from your code, you'll ultimately opening it in an external app, not your UIWebView), because you're getting shouldStartLoadWithRequest before the redirect takes place. You can, though, use NSURLConnection and NSURLConnectionDataDelegate methods to figure out where you'll eventually be redirected.
Long answer:
If you look at shouldStartLoadWithRequest, if you return YES, you'll let the UIWebView follow the redirect requests. Consider the following shouldStartLoadWithRequest:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(#"%#", request.URL);
return YES;
}
If you use this with the a random bit.ly URL, say, http://nyti.ms/Yi6EAk, you'll see the following log:
2013-03-12 21:23:31.418 webtest[6959:c07] http://nyti.ms/Yi6EAk
2013-03-12 21:23:31.511 webtest[6959:c07] http://bit.ly/Yi6EAk?cc=0d7134b272b1004cb954d0400076e9fa
2013-03-12 21:23:31.560 webtest[6959:c07] http://www.nytimes.com/2013/03/13/us/politics/ryans-plan-aims-to-balance-budget-in-10-years.html?hp&_r=0
That third call to shouldStartLoadWithRequest is the actual URL for which I defined the bit.ly redirect URL, i.e. the final destination of two consecutive redirects. But if your shouldStartLoadWithRequest returned NO, then you'll never figure out to which site it would ultimately be redirected. (And by the way, your shouldStartLoadWithRequest should definitely return YES or NO ... your sample doesn't return either.)
As you can see, because shouldStartLoadWithRequest happens before the redirect has a chance to take place, you'll see each and every redirect taking place. Depending upon what the resulting site does, you may see subsequent shouldStartLoadWithRequest calls as the page retrieves extra content. This makes this an awkward mechanism for finding out what site to which you were ultimately redirected.
If you really need the site you're being redirected to, you might want to use NSURLConnection, instead. While that is generally used for actually retrieving the data from the server, it can also be used for capturing the redirects (but not suffering from the problems of the UIWebViewDelegate method shouldStartLoadWithRequest, where it's tough to differentiate between genuine redirects from just random additional content that the page may subsequently request).
So consider the following:
self.url = [NSURL URLWithString:#"http://nyti.ms/Yi6EAk"];
NSURLRequest *request = [NSURLRequest requestWithURL:self.url];
[NSURLConnection connectionWithRequest:request delegate:self];
You can then implement connection:willSendRequest:redirectResponse:, a NSURLConnectionDataDelegate method, that will track the various redirects:
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
{
self.url = request.URL;
return request;
}
Clearly, because you're using NSURLConnection to track the redirects, but we don't really care about the responseData that we generally use NSURLConnection to retrieve, we can just cancel the connection as soon as you receive a good response (confident that we already know to which site were finally redirected):
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[connection cancel];
NSLog(#"Ok, we now know that the resulting URL is %#", self.url);
}
You might, by the way, also want to capture connection errors, for example:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"Ok, we failed trying to retrieve data from %#", self.url);
}
In closing, it's worth pointing out that this technique, of making the HTTP request with NSURLConnection and letting it follow the whole series of redirects, is a pretty inefficient process, so you'll have to decide whether it's worth it. But this is one way to figure out where your HTTP redirects will lead you.
One final caveat, this captures traditional redirects, but if the page is doing any client-side JavaScript redirects, I don't think this technique will work. It works for the vast majority of redirected HTTP requests, though.

Related

UIWebView not loading https link

I am trying to load one secure HTTP url into UIWebView, but it throws me an "untrusted certificate error" i.e error -1202. NSURLDomainError.
I searched on stackoverflow and found out this solution and implemented the same:
UIWebView to view self signed websites (No private api, not NSURLConnection) - is it possible?
As per this solution after receiving a callback to :
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
We again load the secure http url to webview, and as we have already done the authentication pass,
it loads the data.Also, the credential the server uses are my personal crendetials, so i am mentioning the NSURL
NSURLCredential *newCredential = [NSURLCredential credentialWithUser:#"username"
password:#"password"
persistence:NSURLCredentialPersistenceForSession];
But my problem here is, it just goes into some kind of loop, and i am not able to get any of the callback of UIWebView. Any solution or any idea what the problem will be?

Ajax timeout in UIWebView

I'm working on an iOS app that displays a webpage through a UIWebView. The page contains between 20 and 40 buttons. Each one sends an ajax request to the server. It works for the most part, but the server is a desktop app and could close at any moment for any reason with no warning. With these ajax requests, there is no output displayed to the user so if the server went down, the user would have no idea. I would like to be able to be notified of an ajax timeout and then give the user the options to either try again or go back to the home screen. I can't seem to find a way to have the UIWebView be notified of any failed ajax requests.
At first I thought I could simply use the webView:didFailLoadWithError: method of the UIWebViewDelegate, but apparently it's not notified of failed ajax requests.
Any suggestions are greatly appreciated
If when a failure occurs, there is JavaScript code that is invoked in your Ajax failure handlers that makes a location.href change then the UIWebViewDelegate shouldStartLoadWithRequest: method will get called, giving you the chance to do something whenever there is a failure. i.e.
In the Ajax timeout handling code:
window.location.href = "myscheme://AjaxFailure";
In your UIWebViewDelegate handling code:
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(#"shouldStartLoadWithRequest: url: %#", [[request URL] absoluteString]);
NSURL *URL = request.URL;
if ([URL.scheme isEqualToString:#"myscheme"])
{
... notify the user there was an error

webView and 'timeout'

I have question, in my application I need to have a connection and a webview to display the web content.
The problem is that I have a username and a password which need to be right in order to get a connection. Now I want to have an alert that comes up if the username is wrong. The
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
does not work for this, because if the username or the password is wrong it will continue loading (even though it won't display anything).
Is there a possibilty to 'tell' webview to set a time interval and then display an alert?
THANK YOU SO MUCH!
If you're authenticating with your server via HTTP authentication (per your comment above) then I would do this differently. When the user enters their username/pwd, make a request to any page (something lightweight) using NSURLConnection. If the username/password are incorrect you can handle the failures in the NSURLConnectionDelegate methods and prompt your user for a new username/password.
You can also do this with the iOS 5 version of NSURLConnection instead of the delegate methods but the docs aren't very good.
When this first request succeeds then load the web view.
EDIT: added example of using NSURLConnection to check if HTTP auth succeeds
Ok, here's a simple example. You can make this better by allowing NSURLConnection to load your intended request and if it succeeds load the response data into the webView vs initiating a new request. Read the URL Loading System docs to learn more about how to use NSURLConnection.
In your webView delegate, initiate an NSURLConnection with a request to the same domain as the webView request with the user credentials as follows:
- (void)performHTTPAuthRequest
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://%#:%##test.starsightings.com", username, password]]];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
}
// if the request succeeds, credentials must be good so load the webView
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// request completed successfully so username/password must be good
[self.webView loadRequest:theRequest];
}
// ignore the failure
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
}
// if we get an auth challenge, the credentials are bad so cancel the request and alert the user
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[[challenge sender] cancelAuthenticationChallenge:challenge];
// alert user that username/pwd is invalid
}

Embedding a Smart URL inside an App

I want to embed a URL inside an iPhone app, that will take the user to a landing page which later on I can redirect that URL to an iTunes App link once it becomes available, all that without resubmitting the App for approval again. Is this possible?
Thanks
Yes. Use the openURL: method of UIApplication to open your landing page URL. When you want to swap it out, have your favourite server-side language perform a 301 or 302 redirect, depending on whether the redirect is permanent or not.
Having said that, if all you want to do is direct people to rate the app or something similar to that, you don't need to swap it out; the application ID is all you need to construct the URL and iTunes Connect gives you that when you register the app, before you upload the binary.
You can use a short URL or any other URL with a redirection, then in your code intercept the redirection to avoid opening Safari if it's not necessary:
Create a NSURLConnection:
NSURL *shortURL = [NSURL URLWithString:#"http://tinyurl.com/xxyy"];
NSURLConnection *conn = [[NSURLConnection alloc]
initWithRequest:[NSURLRequest requestWithURL:shortURL]
delegate:self
startImmediately:YES];
Define some delegate methods. In your class, define an NSURL property finalDestination to hold the final URL.
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)response
{
finalDestination = [response URL];
return request;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (!finalDestination) {
/* redirect failed, use backup url */
finalDestination = /* backup url */
}
[[UIApplication sharedApplication] openURL:finalDestination];
}
Also, check Apple Technical Q&A Launching the App Store from an iPhone application.
(Note: ARC code)

Handling redirects correctly with NSURLConnection

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.