defunct NSURLConnection never executes or times out - ios

I'm running a LOT of asynchronous (delegate, not block) NSURLConnections simultaneously, and they all come back very quickly as I'm hitting a LAN server.
Every so often, one NSURLConnection will go defunct and never return.
connection:willSendRequest: is called but connection:didReceiveResponse: (and failure) is not.
Any ideas? I'm wondering if I should make a simple drop-in replacement using CFNetwork instead.
Edit: There's really not much code to show. What I've done is created a wrapper class to download files. I will note that the problem happens less when I run the connection on a separate queue - but still happens.
The general gist of what I'm doing is creating a download request for each cell as a tableview scrolls (in cellForRowAtIndexPath) and then asynchronously loading in an image file to the table cell if the cell is still visible.
_request = [NSMutableURLRequest requestWithURL:_URL];
_request.cachePolicy = NSURLRequestReloadIgnoringCacheData;
_request.timeoutInterval = _timeoutInterval;
if(_lastModifiedDate) {
[_request setValue:[_lastModifiedDate RFC1123String] forHTTPHeaderField:#"If-Modified-Since"];
}
_connection = [[NSURLConnection alloc] initWithRequest:_request
delegate:self
startImmediately:NO];
[_connection start];
As requested, instance variables:
NSMutableURLRequest *_request;
NSURLConnection *_connection;
And delegate methods:
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response {
NSLog(#"%# send", _URL);
return request;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"%# response", _URL);
_response = (id)response;
// create output stream
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
_receivedLength += data.length;
_estimatedProgress = (Float32)_receivedLength / (Float32)_response.expectedContentLength;
[_outputStream write:data.bytes maxLength:data.length];
// notify delegate
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// close output stream
// notify delegate
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"%# failure", _URL);
// notify delegate
}
- (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if(_credential && challenge.previousFailureCount == 0) {
[[challenge sender] useCredential:_credential forAuthenticationChallenge:challenge];
}
}

After poking around in profiler, I found a lead, and it gave me a hunch.
My credentials were failing (not sure why...) and so previousFailureCount was not 0, and hence I wasn't using my credential object.
Changed the code to this and I have no problems:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if(_credential) {
[[challenge sender] useCredential:_credential forAuthenticationChallenge:challenge];
}
}

A NSURLConnection will send either didReceiveResponse or didFailWithError.
Often, you're dealing with timeouts before didFailWithError occurs.

Related

NSURLConnection cancellation callback

Is it possible to catch NSURLConnection cancel using a callback?
If I'm using this code
-(void) pleaseStopDownload {
cancelled = YES;
[conn cancel];
conn = nil;
[self myUpdateUImessage];
}
from time to myUpdateUImessage is called before this callback
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"didReceiveData");
if (!cancelled) {
//this code inside brackets suddenly is calling:(
//not always but from time to time
summ += data.length;
if (_progressHandler != nil)
_progressHandler(data, summ, max);
} else {
return;
}
}
So the User interface is not updated properly! That is, the final UI is shown than progress UI.
EDIT
the problem was about
NSOperationQueue *tempQueue = [[NSOperationQueue alloc] init];
[conn setDelegateQueue:tempQueue];
correct NSQueue is NSOperationQueue *tempQueue = [NSOperationQueue mainQueue];
Is it possible to catch NSURLConnection cancel using a callback?
No.
From the official documentation here:
After this method is called, the connection makes no further delegate method calls.
This means you should handle the UI cleanup as soon as you call cancel and not rely on the _cancelled variable because - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data is not expected to be invoked anymore.
What I advice, is to call a cleanup method from your cancellation code:
-(void) pleaseStopDownload {
[conn cancel];
conn = nil;
[self handleCancelledDownload];
}

NSURLConnection making Asynch call a Synch and handle self signed certificate

I am trying to make a Asynchronous Call , a Synchronous one. I know its not a better idea to do it. But, I do need such to code to handle Auth Challenge of Self Signed Certificate while Keeping the call still as Synchronous.
But, I am not sure whether it is a perfect way to make Asycnh call a Synch one.
-(NSData*) startConnection{
NSURLConnection *conn=[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
while(!isFinished && [[NSRunLoop currentLoop] runMode: NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]){
}
return responseAppData;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
//Code to handle Certificate
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[responseAppData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
isFinished=YES;
}
I also thought of using the while Loop as below, so which one should be used?
while(!isFinished ){
}
Actually it's the opposite. If you want to handle these NSURLConnectionDelegate methods, you need to use asynchronous calls, NOT synchronous. Otherwise the delegates are never called.
typedef void (^onDownload)(NSData *data);
#property (nonatomic,assign) onDownload block;
-(void) startConnectionwithBlock:(onDownload) pBlock;{
self.block = [pBlock copy];
}
-(void) connectionDidFinishLoading:(NSURLConnection *)connection{
block(self.data);
}

NSURLConnection: Use all CA certificates installed on device

I want to access some web ressources using HTTPS in a custom iOS 6 app. Some target servers are using a certificate which is signed by a CA that is not by default included in iOS, but was manually added to the keychain of the device. Therefore all URLs can be opened in Safari without any warning or error.
What I want to achieve is the same behavior as Safari: I want to load the websites if Safari would have trusted them, or deny to load them in case of any error. As the installed certificates can change from case to case I do not want to manually include any certificates in the application ressources, which is what many questions here at SO are about.
My problem is that I do not get SecTrustEvaluate to return kSecTrustResultProceed. Do you have any idea what I can do?
If my canAuthenticateAgainstProtectionSpace returns NO, iOS handles the server certificate check on itself, but it does not seem to check for additionally installed certificates (as Safari does).
Here is some code to try and understand what I got so far:
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadURLWithString:#"https://myserver.com"];
}
+ (BOOL) isChallenge: (NSURLAuthenticationChallenge*) challenge validForConnection: (NSURLConnection*)conn{
SecTrustRef serverTrust=[[challenge protectionSpace] serverTrust];
//Some magic here?
// Check Server Certificate
SecTrustResultType evalResult;
if(SecTrustEvaluate(serverTrust,&evalResult) != errSecSuccess){
NSLog(#"Call to SecTrustEvaluate failed");
return NO;
}
if(evalResult != kSecTrustResultProceed){
NSLog(#"Server certificate invalid");
return NO;
}
NSLog(#"Server certificate valid");
return YES;
}
- (void)loadURLWithString: (NSString*)str{
NSURLConnection *conn =
[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:str]] delegate:self];
[conn start];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
if([[self class] isChallenge:challenge validForConnection:connection])
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
else
[challenge.sender cancelAuthenticationChallenge:challenge];
}
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace{
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Failed with error: %#",error);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(#"loading complete");
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
}
What you're trying to do is not permitted. For more info, see this thread in the Apple developer forums:
https://devforums.apple.com/message/660579#660579

iOS NSURLConnection not downloading files from certain URLs

I have an NSURLConnection in a tableview cell subclass that can download most files. I noticed, however, that some fail to start downloading, and time out. An example would be this URL, which is just a test zip file that downloads fine in any other browser. Heres my code for the download
-(void)downloadFileAtURL:(NSURL *)url{
self.downloadedData = [[NSMutableData alloc] init];
self.url = url;
conn = [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:self.url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:1200.0] delegate:self startImmediately:YES];
}
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSHTTPURLResponse*)response
{
int statusCode = [response statusCode];
if (statusCode == 200){
self.fileName.text = response.URL.lastPathComponent;
self.respo = response;
expectedLength = [response expectedContentLength];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.downloadedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
CFStringRef mimeType = (__bridge CFStringRef)[_respo MIMEType];
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
CFStringRef extension = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension);
NSString *fileName = [NSString stringWithFormat:#"%#.%#", [[_respo suggestedFilename] stringByDeletingPathExtension], (__bridge NSString *)extension];
[[NSFileManager defaultManager] createFileAtPath:[[self docsDir] stringByAppendingPathComponent:[NSString stringWithFormat:#"Downloads/%#", fileName]] contents:_downloadedData attributes:nil];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Download failed with error: %#", error);
}
Anybody see anything that might cause this?
Heres the error:
Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo=0x1fd2c650
{NSErrorFailingURLStringKey=http://download.thinkbroadband.com/10MB.zip,
NSErrorFailingURLKey=http://download.thinkbroadband.com/10MB.zip,
NSLocalizedDescription=The request timed out., NSUnderlyingError=0x1fdc90b0 "The request timed out."}
"I have an NSURLConnection in a tableview cell subclass " - never do this. As Sung-Pil Lim already pointed out correctly, TableView Cells will be reused which may cause this issue.
Anyway, the response data of your connection is a property of the model. The model might encapsulate how it gets to this data. If that data is not immediately available once it will be accessed, it should provide a "placeholder" value instead and start an asynchronous task which retrieves this data.
Suppose a model's property, an image, will be accessed by the view controller in order to be displayed by a view. The model has not yet loaded its actual image - and thus it returns a "placeholder image" in order to let the view display something. But at the same time the model is starting an asynchronous task to load the image. When this connection is finished loading with the data, the model updates internally its property - thereby replacing the placeholder with the real image. The update of the property should be performed on the main thread - since the UIKit views may access the same property as well.
During initialization, the View Controller has registered as an observer of the model's property (see KVO). When the model's property is updated, the controller gets notified. The View Controller then performs appropriate actions so that the view will be redrawn and displays the new updated value.
Your model should have a "cancel" method, which will be send to the model from the controller when the actual value of the model's property is not required anymore. For example, the user switched to another view (see viewWillDisappear).
I tried your codes.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.downloadedData appendData:data];
NSLog(#"%d", data.length);
}
2013-05-04 01:51:13.811 SomethingTodo[2732:c07] 1124
2013-05-04 01:51:13.856 SomethingTodo[2732:c07] 1448
2013-05-04 01:51:14.075 SomethingTodo[2732:c07] 1448
2013-05-04 01:51:17.180 SomethingTodo[2732:c07] 1448
2013-05-04 01:51:17.295 SomethingTodo[2732:c07] 1448
It's working... on ViewController
'request timeout error' was brought to network connection. or...
Are you resuing UITableViewCell? If you initialize for cell reuse codes deal with connection. maybe bring to trouble. Just i thought.
If you attach more your codes. Could I help you more then this.
I would start with a clean slate and just use basic code to work the download. Load in lots of NSLog(s) to track everything. If that works, keep adding your custom code and see if you stumble across an error. I suggest basic NSURLConnection code:
-(void)startDownloading:(NSString *)URLaddress{
NSLog(#"start downloading from: %#",URLaddress);
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:[URLaddress stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
__unused NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self startImmediately:YES];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(#"didReceiveResponse: %#", response);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSLog(#"didReceiveData");
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Connection failed! Error - %# %#",[error localizedDescription], [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(#"connectionDidFinishLoading");
}
try with HCDownloadViewController and you can check which url is not downloaded. and next time sync for that particular url which is not downloaded.
.h file
#import "HCDownloadViewController.h"
#interface HomeViewController_iPhone : UIViewController<HCDownloadViewControllerDelegate>
{
HCDownloadViewController *tblDownloadHairStyle;
}
#property (nonatomic,retain) HCDownloadViewController *tblDownloadHairStyle;
.m file
#define kAppDirectoryPath NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)
#synthesize tblDownloadHairStyle
- (void)viewDidLoad
{
[super viewDidLoad];
tblDownloadHairStyle=[[HCDownloadViewController alloc] init];
tblDownloadHairStyle.delegate=self;
}
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSHTTPURLResponse*)response
{
[self createDocumentDirectory:#"Downloaded_HairStyle"];
NSString *pathHair=[self getDocumentDirectoryPath:#"Downloaded_HairStyle"];
tblDownloadHairStyle.downloadDirectory = pathHair;
////You can put url in for loop, it create queue for downloading.
[tblDownloadHairStyle downloadURL:[NSURL URLWithString:#"yourUrl"] userInfo:YourResponseDictonary];
}
-(void)createDocumentDirectory:(NSString*)pStrDirectoryName
{
NSString *dataPath = [self getDocumentDirectoryPath:pStrDirectoryName];
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath])
[[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:NULL];
}
-(NSString*)getDocumentDirectoryPath:(NSString*)pStrPathName
{
NSString *strPath = #"";
if(pStrPathName)
strPath = [[kAppDirectoryPath objectAtIndex:0] stringByAppendingPathComponent:pStrPathName];
return strPath;
}
#pragma mark-
#pragma mark-HCDownloadViewController Delegate Method
- (void)downloadController:(HCDownloadViewController *)vc startedDownloadingURL:(NSURL *)url userInfo:(NSDictionary *)userInfo {
}
- (void)downloadController:(HCDownloadViewController *)vc finishedDownloadingURL:(NSURL *)url toFile:(NSString *)fileName userInfo:(NSDictionary *)userInfo {
if (vc==tblDownloadHairStyle) {
if ([tblDownloadHairStyle numberOfDownloads]==0) {
NSLog(#"AllDownLoad are complete");
}
}
}
- (void)downloadController:(HCDownloadViewController *)vc failedDownloadingURL:(NSURL *)url withError:(NSError *)error userInfo:(NSDictionary *)userInfo {
NSLog(#"failedDownloadingURL=%#",url);
}
https://github.com/H2CO3/HCDownload
accept any response with http response code range 200-299 and disable caching on the http-connector.
double check your url address conforms to RFC 2396. so it must include HTTP://
Do you have any libraries (TestFlight, UA, etc) in the project? Try removing them and re-test. We had an app that used NSUrlConnection with TestFlight SDK that caused all sorts of sporadic network problems.
NSURLConnection timing out
ASIHTTPRequest request times out
https://github.com/AFNetworking/AFNetworking/issues/307

how to get response body in IOS?

Now I want to get the response body. I'm a new guy in IOS developer. I only kown I can use response.statusCode to get httpstatuscode ,such like 400,500 and so on. But how to get response body. Maybe allheaderFileds or data or error.Description?
The full details are available at: URL Loading System Programming Guide
You'll need a object that is the delegate of the NSURLConnection. You'll need to have a receivedData member variable and you'll need to implement the delegate methods:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[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)connectionDidFinishLoading:(NSURLConnection *)connection
{
// do something with the data
// release the connection, and the data object
[connection release];
[receivedData release];
}

Resources