What are alternatives to NSURLConnection for chunked transfer encoding - ios

I've checked for other questions relevant to this, but the only answer is "Use ASIHTTPRequest" as this is no longer being developed I wanted to ask what alternatives people are using, whilst working on our SDK I came across a lot of strange behaviour in NSURLConnection when receiving data from the server.
We tracked it down to the fact that NSURLConnection doesn't deal well with responses in chunked-encoding. Or at least so we read in this question here NSURLConnection and "chunked" transfer-coding
Some developers we were talking to say it gets better in iOS 5, we need to make sure that our SDK is backwards compatible with iOS 4.3 at least.
I want to confirm this is infact an issue in NSURLConnection, and how people are dealing with it.
All the alternatives I've found so far are based off of NSURLConnection and I'm assuming as such will have the same flaw. ASIHTTPRequest did in fact work because it's based a little lower than NSURLConnection, but were looking for alternatives in the knowledge it's no longer supported.
A list of other libraries looked at are:
Restkit,
ShareKit,
LRResty,
AFNetworking,
TTURLRequest
I'm aware there are similar questions here Is RESTKit a good replacement for ASIHTTPRequest? and here ASIHTTPRequest alternative But both of the solutions are based off NSURLConnection.
EDIT: I noticed I pointed to the wrong question at the start of my post, so thats updated. It points to a thread from 2008, and i've seen similar ones but none that are recent.

Chunked transfers are supported by NSURLConnection. I use them.
Define some props:
NSMutableData * responseData;
NSURLConnection * connection;
Establish a connection
NSURL *url = [NSURL URLWithString:#"...."];
self.responseData = [[NSMutableData alloc] initWithLength:0] ;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
Register your callback method for connection established
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// You may have received an HTTP 200 here, or not...
[responseData setLength:0];
}
Register your callback method for "chunk received"
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSString* aStr = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(#"This is my first chunk %#", aStr);
}
Register your "connection finished" callback:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
}
And finally, register you "connection failed" callback:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"Something went wrong...");
}

Just to chime in for the next person that gets here and still can't get NSURLConnection to work with chunk encoded data.
NSURLConnection will work with chunked encoding, but has non-disclosed internal behaviour such that it will buffer first 512 bytes before it opens the connection and let anything through IF Content-Type in the response header is "text/html", or "application/octet-stream". This pertains to iOS7 at least.
However it doesn't buffer the response if Content-Type is set to "text/json". So, whoever can't get chunked encoded NSURLConnection responses to work (i.e. callbacks aren't called) should check the response header and change it on the server to "text/json" if it doesn't break application behaviour in some other way.

There aren't any alternatives I'm aware of.
All the other libraries are built on top of NSURLConnection. Though you could use one of the non-iOS libraries, eg. libcurl.
ASIHTTPRequest is the only library I'm aware of that's built on top of the CFNetworking layer instead. This was (perhaps indirectly) the main reason the original developer stopped working on it - because it doesn't use NSURLConnection it has a lot of code.
It's probably not strictly correct to say that ASIHTTPRequest is no longer supported. It is true that the original developer no longer works on it, but if you look at the github commits you'll see it is still being worked on by other people. A lot of people still use it, for various reasons, myself included.
Having said all that, to go back to the problem you have: I'm not sure a 3 year old thread is necessarily a definitive reference to prove that a 1 year old release (ie. iOS 4.3) of NSURLConnection doesn't support chunked transfers. Chunked transfers are used so much on the web that it seems highly unlikely it would have a problem this large and obvious. It's possible there is something very particular to the server that you're using that is causing the issue.

Related

STOMP Websocket sometimes doesn't response

I have a service using STOMP Websocket, I use WebsocketStompKit library
https://github.com/rguldener/WebsocketStompKit
NSURL *websocketUrl = [NSURL urlWithString:#"ws://xxx/websocket"];
STOMPClient *client = [[STOMPClient alloc] initWithURL:websocketUrl websocketHeaders:nil useHeartbeat:NO];
[self.client connectWithHeaders:nil completionHandler:^(STOMPFrame *connectedFrame, NSError *error) {
NSString *status = [Utils getStringIgnoreNull:connectedFrame.command];
if ([status isEqualToString:#"CONNECTED"]) {
NSLog(#"-------Connected to socket server!");
[self subscribe];
}
}];
I followed the instructions to setup STOMPClient, it works ok.
But sometime, it doesn't run to completionHander block, I waited a long time but it still don't response anything. My internet connection is very fine.
So anyone know the solution? Or can you give me another library to do this.
Thank so much.
As it turned out in this and this Github issue conversations, the problem was on the server side. If anyone comes here looking for help, I suggest going through these libraries issue pages and trying to match your problem, or contact the creators of Jetfire and Starscream (if you are implementing them) because they are very friendly and do answer your upgrade/new feature issues pretty quickly!

iOS large file download ~1GB files

For my bachelor thesis I have to implement an eLearning app for iOS. As you can see in my title, I have to download a file in the documents directory that has a size of nearly 1 or 2 GB. I'm very new at iOS-Development, so I would appreciate every tip how i can handle that.
Many thanks in advance!
Its good that you have read about NSURLConnection and ASIHTTP. But as you must be aware ASIHTTP is no longer actively developed/maintained. NSURLConnection is good to understand and learn the basics.
For day to day use, I'd suggest you use AFNetworking. It is simple to use and contains example for understanding how to use it.
For downloading large files it is recommended to write the downloaded data directly to a file rather than storing it in the memory. Using AFNetorking you can do this by,
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:#"download.zip" append:NO];
I have not tried downloading that large data myself, but I'm sure this will be a good start point for you.
AFNetworking: https://github.com/AFNetworking/AFNetworking
API documentation: http://cocoadocs.org/docsets/AFNetworking/2.0.0-RC2/
Happy coding!
Well, since this will very likely take a while to download I would definitely recommend doing this asynchronously using NSURLRequests and NSURLConnections.
My biggest concern though, is the pure size of this download and how this will likely not fit into RAM so this download may not actually be as easy as it seems. You can give it a shot, but what I would recommend is breaking this into multiple files/downloads if possible.
I will post code in a minute when I find code I have previously written.
Added Code
First off, make your class that needs to do the downloading to be an NSURLConnectionDelegate and `NSURLConnectionDataDelegate.
Then you can implement the download request as follows.
NSMutableData *linkData = [[NSMutableData alloc] init];
NSURLRequest *linkRequest = [[NSURLRequest alloc] initWithURL:youURL cachePolicy:NSURLRequestReloadRevalidatingCacheData timeoutInterval:10.0];
NSURLConnection *connect = [[NSURLConnection alloc] initWithRequest:linkRequest delegate:self];
The variable linkData should really be declared in the interface file and then allocated in the implementation, but for brevity I just created it as above.
Then you need the following methods which will be called
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[linkData appendData:data];
}
-(void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
// handle a fail
}
-(void) connectionDidFinishLoading:(NSURLConnection *)connection {
// do what you need when download finishes
}

NSData dataWithContentsOfURL: not returning data for URL that shows in browser

I am making an iOS client for the Stack Exchange API. After a long, drawn out fight I finally managed to implement authentication - which gives me a token I stick into a URL. When the token is valid, the URL looks like this:
https://api.stackexchange.com/2.1/me/associated?key=_____MY_SECRET_KEY______&access_token=_____ACCESS_TOKEN_:)_____
which, when valid, brings me to this JSON in a webpage:
{"items":[{"site_name":"Stack Overflow","site_url":"http://stackoverflow.com","user_id":1849664,"reputation":4220,"account_id":1703573,"creation_date":1353769269,"badge_counts":{"gold":8,"silver":12,"bronze":36},"last_access_date":1375455434,"answer_count":242,"question_count":26},{"site_name":"Server Fault","site_url":"http://serverfault.com","user_id":162327,"reputation":117,"account_id":1703573,"creation_date":1362072291,"badge_counts":{"gold":0,"silver":0,"bronze":9},"last_access_date":1374722580,"answer_count":0,"question_count":4},...
And I get the correct JSON with this code:
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"https://api.stackexchange.com/2.1/me/associated?key=__SECRET_KEY_:)__&access_token=%#", [[NSUserDefaults standardUserDefaults] objectForKey:#"token"]]];
NSData *jsonData = [NSData dataWithContentsOfURL:url];
if (jsonData)
{
NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:nil];
}
When I manually invalidate the token, however, the URL still looks the same, and the page in a browser displays this:
{"error_id":403,"error_name":"access_denied","error_message":"`key` is not valid for passed `access_token`, token not found."}
However, dataWithContentsOfURL: is always nil. Why? What am I doing wrong?
I do get an NSError returned:
Error Domain=NSCocoaErrorDomain Code=256 "The operation couldn’t be completed. (Cocoa error 256.)" UserInfo=0x1dd1e9f0 {NSURL=https://api.stackexchange.com/2.1/me/associated?key=key((&access_token=to‌​ken))}
NSCocoaErrorDomain Code=256 actually means a "file system or file I/O related error whose reason is unknown".
Why you get this error is likely because using dataWithContentsOfURL: will not work with that remote URL - or maybe because of the query params which contain the authentication and the token. Thus, you get the "weird" error.
In general, NSData's dataWithContentsOfURL: should only be use to access local file resources.
In order to solve your problem, you should improve your code in two steps:
1) Use NSURLConnection's convenient class method
+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
The block defines what to do with the response data when the request finished. Generally, first check the error parameter, then status code of the response and Content-type - in this order.
2) Replace the former with your own instance method (or one from a third party) with a similar signature but which is much more sophisticated.
Approach #2 enables you to implement and use the following important features
Cancellation
customize authentication in every aspects
load body data to files
process received chunks simultaneous
perform multiple requests in a queue which controls the number of simultaneous connections
and a couple more.
Approach #2 is oftentimes implemented as a subclass of NSOperation and encapsulates a NSURLConnection object (which you need to cancel the connection).
You'll find answers of how to use NSURLConnection in asynchronous mode implementing the delegates. Also, there are third party solutions.
You might find the official documentation invaluable, too:
Using NSURLConnection
For a quick start, you may take a look at my "Simple GET request" class on Gist:
SimpleGetHTTPRequest
This class is NOT based on NSOperation, but it can be modified easily. Consult the official documentation of NSOperation how to make a subclass. This is basically easy, but has a few important things (KVO) which you should get correct.
In my case, adding AppTransportSecuritySettings dictionary into info.plist and setting key AllowArbitraryLoads to true.
Fixed my problem...
Hope it helps new developers.
Your URL might be having a space that's why it returns nil.Just replace the space in URL with a '+' :
stringByReplacingOccurrencesOfString:#" " withString:#"+"
I had this problem when I changed my folder structure. It gave me other NSError codes such as 512 and 4 for any file operations (local and web). The solution was to delete my IOS Simulator folders (Library\Developer\CoreSimulator).
If you are trying to access a remote url via HTTP and using XCode 7 or later you may get a NSCocoaErrorDomain returned from [NSData dataWithContentsOfURL:].
The root cause of this may actually be your "App Transport Security Settings". By default iOS doesn't allow arbitrary loading of URLS.

NSURLConnection always failing after restarting app

I realize this is a vague question, but I'm wondering if anyone else has observed this. Here is my code for calling the NSURLConnection
// Get data from server
NSString *host = #"www.hostname.com";
NSString *urlString = [NSString stringWithFormat:#"/theRestOfTheURL"];
NSURL *url = [[NSURL alloc] initWithScheme:#"http" host:host path:urlString];
DLog(#"URL is %#", url);
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
receivedData_ = [[NSMutableData data] retain];
NSURLRequest *theRequest=[NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:15.0];
// create the connection with the request
// and start loading the data
self.powerPlantDataConnection = [[[NSURLConnection alloc] initWithRequest:theRequest delegate:self] autorelease];
[url release];
When I first load the app it works fine, and I can call it repeatedly without any problem. But if I close the app and reopen it, the
(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
delegate method gets called every time, with a request timed out error message. I have two different view controllers where I am making calls to two different URLS, and both of them fail every time after closing and reopening the app.
Can anyone think of any reason why this might be happening? I'm not sure where to start looking. What could be the cause of a request timed out error? There should be nothing wrong with the request since it works when I first run the app.
Edited to add that it seems I only have this problem on my device, not on the simulator.
Wish you had shared some chrash log(especially with clear definition like
[error localizedDescription] class method..)
As you have said it is going to timeout(your request). and since your way of creating the objects is too messy, you make the work bigger for your system. I suggest using GCD when downloading data and especially in situations like yours, having different interfaces and urls..
A suggestion
You can create your url object like this:
NSURL *url = [NSURL urlWithString:[NSString stringWithFormat:#"http://%#/%#?key1=%#&key2=%#", yourDomain, targetFile, value1, value2]];
I switched over to using ASIHTTPRequest, which improved things but I would still get stuck in situations where I was getting timed out errors every time I tried to refresh. I looked and asked around, and eventually found that disabling calls to TestFlight solved my issue. More information here:
ASIHTTPRequest request times out
and here:
github.com/pokeb/asi-http-request/issues/320

How to test if a file at an external URL exists without downloading it?

I would like to test if a URL exists without loading any actual data.
I was thinking of initiating a NSURLConnection, and then using these two delegate methods to check the status:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// Connection failed ... presumably this is a server issue, and I don't know if the URL exists or not.
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
int status = [((NSHTTPURLResponse *)response) statusCode];
[connection cancel];
// Do something with the status
}
Would this be a sensible way? The files I'm testing could potentially be very large, so I want to make sure that the actual file is not downloaded.
Thanks,
Ron
p.s. I'm looking at adding this because Apple forced my App to stop backing up files downloaded from the internet. Instead, I store the files in non-backed up space, and in my backed up database I keep track of the original location of the file. Since the file is intended to be a permanent part of the user's library, I would like to periodically test if a file is not accessible and then move it into backed up space (so it will survive a restore to a new device, for example). I'm very annoyed at Apple for forcing me to make this change as I can imagine customers losing important data.
p.p.s. For some strange reason, these delegate methods no longer appear in the documentation for NSURLConnectionDelegate Protocol. I'm assuming the documentation is just messed up.
Personally, I would send a raw request or modify the head to use HEAD which is a standard HTTP header.
It has the same result type as GET, so if you can check the result headers, see if its 403,404 or 201 then you're good.
http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html

Resources