Cocoa Touch: How to detect a missing file on the download server - cocoa-touch

My iOS app downloads document files from a server using the NSURLConnection class. I use it asynchronously.
It can happen that a document is missing from the server (configuration error). I expected NSURLConnection to call my delegate error method:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
But it doesn't. Instead, it happily downloads the HTML error 404 web page returned by the server. The only workaround I found was to check the NSURLResponse MIMEtype property and fail myself when it is text/html. It works fine, but I don't like it because it precludes me from supporting html documents at a later date.
How can I check whether a requested URL is indeed present on the server?
Thanks

In the - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response delegate method you can ask the response for it's error code.
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
if (httpResponse.statusCode == 404) {
...
//etc

When you receive the response (in -connection:didReceiveResponse:), you get to query the response. Because you're using HTTP, it will be a kind of NSHTTPURLResponse, though you may want to test for that. Anyway, it will tell you a -statusCode, which might be 200, 404 or something else.

Related

Handling POST redirects with block-based NSURLConnection API

I have to solve the problem that NSURLConnection automatically redirects POST redirects with a 302 response as GET requests (and drops the HTTP body and some headers).
I found and successfully tried the NSURLConnectionDataDelegate solution (e.g. here), implementing:
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
and throwing away/fixing the problematic request. This works.
However. As I understand it, this means I have to use the delegate APIs like:
[NSURLConnection connectionWithRequest:request delegate:self];
Is there a way to fix the redirect using the much more comfortable block-based asynchronous API as well?:
+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler

NSURLCredential-credentialForTrust vs username:password

I am new with iOS and https stuffs. I am a beginning software student. I want to write an app that extract the data (such as class number, announcements, etc) from my uni account and show me on my app.
Normally, I go to my uni student page, login with my username and password, and those information showed up on the browser.
I've tried using NSURLConnection and its delegate. please look at my code below. In the beginning, in the method connection:didReceviveAuthenticationChallenge, I created an NSURLCredential object by [NSURLCredential withUser:password:persistence]. And it was not working. Then I tried to do NSLog, then i just found out that the protectionSpace.authenticationMethod is NSURLAuthenticationMethodServerTrust.
Then, I tried to read Apple's document and some googled sources. But, I can't really get it, and then I edited my code as you seen below (it is my final edition, and its still not working).
The point that i don't really get is: I expect that the server should ask me for my username and password. Instead of that, it asked for credentialForTrust, which based on those documents I've read, they suggest me to evaluate the trust of NSURLConnection delegate against the server. However, the server never asked for username and password. so, how can the server know which account I am accessing.
So, i think it should be some relationship between that certificate(trust) with username and password. I don't really understand how these things work.
I think, my question might not be really clear or something, and it might be foolish. I accept this because I've never learned all these things.
So, please someones explain me of how these things work. You can assume that I have some basic understanding of what is https, SSL, NSURLConnection, NSURLCredential, etc. and please guide me to the solution.
I am appreciate for all your efforts.
Below is my code, (it's not working).
The NSLog() in connection:didReceiveAuthenticationChallenge, print out "NSURLAuthenticationMethodServerTrust"
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return YES;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(#"authenticationMethod is: %#\n",challenge.protectionSpace.authenticationMethod);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}else{
NSURLCredential *creden = [[NSURLCredential alloc] initWithUser:myusername password:mypassword persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:creden forAuthenticationChallenge:challenge];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"didFailWithError: %#\n %#\n",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.myData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[self.myData setLength:0];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *str = [[NSString alloc] initWithData:self.myData encoding:NSASCIIStringEncoding];
NSLog(#"Data in String %#", str);
}
NSURLAuthenticationMethodServerTrust challenges occur when the URL Loading system doesn't trust the server. If you want to avoid those calls, make sure that iOS trusts your server's cert (by installing it manually , or having a trusted root like Verisign sign it).

Street address verification

My user enters a recipients address (Street address not email). I need to verify it with the USPS so I know that it is actually an address.
I am digging through their API right now and I think I understand it but I'm not exactly sure how to go about it with objective-c.
So pretty much it works like so:
I have to create an XML request that contains the recipient name, address, and zip code.
I have to post that to their server
They respond with an XML response
Here is an example of what one of their constructed XML request looks like:
http://SERVERNAME/ShippingAPITest.dll?API=Verify&XML=<AddressValidateRequest% 20USERID="xxxxxxx"><Address ID="0"><Address1></Address1>
<Address2>6406 Ivy Lane</Address2><City>Greenbelt</City><State>MD</State> <Zip5></Zip5><Zip4></Zip4></Address></AddressValidateRequest>
A bit garbled but broken down:
http://SERVERNAME/ShippingAPITest.dll?API=Verify&XML=
<AddressValidateRequest% 20USERID="xxxxxxx">
<Address ID="0">
<Address1></Address1>
<Address2>6406 Ivy Lane</Address2>
<City>Greenbelt</City>
<State>MD</State>
<Zip5></Zip5>
<Zip4></Zip4>
</Address>
</AddressValidateRequest>
My first idea seems obvious but there maybe a better way to go about it. Since the XML feed short, should I go about construction by simple doing something along the lines of:
NSString *request = [NSString stringWithFormat:#"......"]
Where it is filled in and formatted along the lines posted above.
The second question is how to go about correctly sending this to the server?
I simply create a NSURL request and with the URL as the constructed XML string?
Here what I have but I keep getting that the URL was constructed wrong:
- (void)verifyAddress:(Recipient*)_recipient {
NSURL *_url = [NSURL URLWithString:#"http://testing.shippingapis.com/ShippingAPITest.dll?API=Verify&XML=<AddressValidateRequest%20USERID=\"********\"><Address ID=\"0\"><Address1></Address1><Address2>6406 Ivy Lane</Address2><City>Greenbelt</City><State>MD</State><Zip5></Zip5><Zip4></Zip4></Address></AddressValidateRequest>"];
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:_url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
receivedData = [NSMutableData data];
NSString* newStr = [[NSString alloc] initWithData:receivedData
encoding:NSUTF8StringEncoding];
NSLog(#"the response '%#'", newStr);
} else {
// Inform the user that the connection failed.
NSLog(#"error");
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// This method is called when the server has determined that it
// has enough information to create the NSURLResponse.
// It can be called multiple times, for example in the case of a
// redirect, so each time we reset the data.
// receivedData is an instance variable declared elsewhere.
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
// inform the user
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString* newStr = [[NSString alloc] initWithData:receivedData
encoding:NSUTF8StringEncoding];
NSLog(#"the response '%#'", newStr);
// do something with the data
// receivedData is declared as a method instance elsewhere
NSLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
}
I get the following error:
Connection failed! Error - bad URL (null)
My only question now is, am I doing everything ok as far as NSURLConnection goes? I can play around with the URL, I just want to make sure my implementation is ok so Im not running around in circles. :P
You have % 20 in your URL. It should be %20 (no space).
There may be other problems, but that was one was easy to spot. If you are getting an error message, you need to edit your question and paste in the exact error message.
Also, you might consider using Apple's NSURLRequest and NSURLConnection classes, because more people are likely to be familiar with them so it may be easier for you to find help.
Cory, I work in the address validation industry (for SmartyStreets, where what you're trying to do is our specialty in fact) and have seen a lot of similar issues to yours.
We actually used to support an XML-endpoint for our address verification API (LiveAddress). Last year we deprecated it and deployed a new JSON format because the XML was clunky to use and had a lot of problems when it's actually just a simple task (for you, the developer).
So a few things to keep in mind... and while Rob's answer is programmatically comprehensive, these are important to consider also:
The USPS is the official source of addresses for the USA, but its core domain is not providing API service. Especially with recent financial troubles, I suspect that support and maintenance of the API will wane over time.
The License Agreement for the API you're using is quite restrictive. For example:
User agrees to use the USPS Web site, APIs and USPS data to facilitate USPS shipping transactions only. [27 Jan 2012]
Meaning, if you're shipping mail or packages via the USPS by using their API, it's fine, but for any other purpose it's not allowed and it violates the TOS.
I see you're developing for iOS. There's a great JSON library for that called TouchJSON that, in my opinion, is easier to use than XML formats in Objective-C.
While the USPS service does work, they CASS-certify private entities to provide their data at a better value (more specialty, experience, features, etc).
These and other maladies can be remedied by service from a third-party vendor. More details and reasons are documented here. Which provider you choose is up to you, but I'll be happy to personally answer any other address-validation-related questions.

How can i transform a JSON Value in NSString

I want to retrieve the username of the member who publish something in Facebook with my iPhone App.
I got it but in bad format.
My code :
//My method
- (void)request:(FBRequest *)request didReceiveResponse:(NSURLResponse *)response {
NSLog(#"received response %#",response);
//The callback
[_facebook requestWithGraphPath:#"https://graph.facebook.com/me" andDelegate:self];
And i got this in GDB:
received response <NSHTTPURLResponse: 0x1a11e0>
What's the format : JSON i think but how can i transform it in a simple NSString ?
Thanks
Flo.
You are using the wrong method to access the response. You are using a method from the NSURLConnection delegate protocol.
You should implement the following methods as well:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
This method can be called many times, so you'd like to add the incoming data to a NSMutableData instance.
and
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
which will tell you that loading of the data has finished, your response is complete.
When the received data is complete you can create a string with it with.
- (id)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding
I'd recommend a JSON Framework to parse json strings.
You can try with JSON-Framework for parsing JSON.
Anyway... you should look at the NSURLResponse Class Reference...

How to find the 404 error cause, when I am using a webview based application and the server fails in iphone sdk

I am implementing a webview based application, in that I need to find out a way when the 404 error occurred.
Anyone's help will be much appreciated.
Thanks to all,
Monish.
In the webViewDidFinishLoad method, you can also check it this way:
NSCachedURLResponse *resp = [[NSURLCache sharedURLCache] cachedResponseForRequest:webView.request];
NSLog(#"status code: %ld", (long)[(NSHTTPURLResponse*)resp.response statusCode]);
webViewDidFinishLoad() method writes following code and checks status code...
- (void)webViewDidFinishLoad:(UIWebView *)webview {
NSCachedURLResponse *urlResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:webview.request];
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) urlResponse.response;
NSInteger statusCode = httpResponse.statusCode;
}
Here you just need to check the status of the request when it finishes or fails in webview delegate method.`
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
int status = [[[webView request] valueForHTTPHeaderField:#"Status"] intValue];
if (status == 404) {
}
}
If this doesn't help you out. Check this one.
Create an NSURLRequest with the URL you want to load. Then make the connection using NSURLConnection.
NSURLConnection has a delegate method
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
which will give the the response from the server. Please note that if you are making the connection over HTTP, the response will actually be of class NSHTTPURLResponse. The NSHTTPURLResponse can be used to get the status using the following instance method
- (NSInteger)statusCode
Then check if status = 404 or not and if yes then show your alert view. In this way you will be able to show the html page and the alert view both.
Try this, I think that if URL is wrong then the return html is nil. you can handle it there only
NSString *htmlCode = [[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:loadUrl]];
if (htmlCode==nil)
{
// you can handle here with an alert or any message in webview to load.
}
else
{
[myWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:loadUrl]]];
}
[htmlCode release];
You can handle using htmlCode in other method like
- (void)webViewDidFinishLoad:(UIWebView *)webView
if You want to handle it after request.