AFNetworking - How to get data from cache when no internet connection? iOS - afnetworking

Is it really? Or I should check internet connection and get from custom cache data?
For example:
I load via AFNetworking JSON by URL_JSON when good connection. Then connection falls. Then I request URL_JSON and I should get the same JSON as when was good connection.
How it make better?

You can use the following code in the failure block for a request that you want to use the cached response (if available)
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (cachedResponse != nil &&
[[cachedResponse data] length] > 0)
{
// Get cached data
....
}
as mentioned in this link
https://github.com/AFNetworking/AFNetworking/issues/566

Related

iOS: How to stream (live sending) data over HTTP?

I know, this topic's title is pretty general and I thought this problem was easy to solve (I made it on Android very easily). But on iOS... Well, this question exists so....
I have a device and I want to stream data (audio data to be precise) on it. The protocol I have to use is HTTP. Actually, I have to build ans send a single HTTP request containing some data in its body, then, as and when my "producer" (here, a process that resample/encode audio data) gives new data to transmit, send it through this request body. It sounds weird for HTTP, but it's like HTTP multipart without boundary (data are just sent one behind the others).
For example, in Java I initialize a HttpUrlConnection with URL and headers, I precise the Content-Length header to "9999999" (as it is wrote in the targeted device's documentation) with /*my connection*/.setFixedLengthStreamingMode(9999999);. I don't call connect()method but I get the output stream (getOutputStream()) and when my producer gives new data, I just call /*my output stream*/.write(/*my data*/); then /*my output stream*/.flush();
So, I tried with NSURLSession and with low-level APIs NSInputStreamand NSOutputStream. But in each case, I can't achieve that:
With NSURLSession I called uploadTaskWithStreamedRequest: but the callback needNewBodyStream: is only called once (or twice in case of new auth). So I only send one data packet in the input stream.
With NSInputStreamand NSOutputStream connection is close by targeted device without error before I can send any data with standard TCP ending on input stream (except HTTP headers).
I think this is something a lot of people did and they is a way I didn't see, no? Please help me by give me examples (this'll be perfect) or avises about what API I have to choose or a specific parameter to apply?
Thanks!
EDIT
My problem is similar to this but it's an other (shorter) way to explain it.
EDIT 2
Here is my code when I use NSURLSession (I prefer keep using that if possible):
NSURLSessionConfiguration* defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSOperationQueue* operationQueue = [NSOperationQueue mainQueue];
NSURLSession* session = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:self delegateQueue:operationQueue];
NSInputStream* inputStream = [NSInputStream inputStreamWithData:/*data*/];
NSURL* url = /*init from string*/
NSMutableRequest* request : [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = #"POST";
request.HTTPBodyStream = inputStream;
/*Add some headers to request*/
NSURLSessionUploadTask* uploadTask = [session uploadTaskWithStreamedRequest:request];
[uploadTask resume];
To be precise, I didn't put auth information in URL. Delegates's methods are called in this order : didSendBodyData -> didReceiveChallenge (this is where I perform authentication and it works because I received just before a 401 and nothing after) -> needNewBodyStream (this is where I put my data in the input stream for the completion handler) -> didSendBodyData -> didFinishCollectingMetrics -> didCompleteWithError (with code -1005 : kCFErrorDomainCFNetwork). Strange thing is that data is transmited well (the first data packet) because I can heard sound in the target device for some instants.
To be more precise again, here is the network traffic during connection:
Send TCP SYN, ECN, CWR
Rcv TCP SYN, ACK, ECN
Send TCP ACK
Send TCP segment of reassembled PDU (my HTTP request and data without auth) (x7, each followed by an ACK response)
Rcv HTTP 401 (because required digest)
... TCP Succession of ACK, FIN/ACK, SYN/ECN/CWR...
Send TCP segment of reassembled PDU (my HTTP request and data with auth) (x7, each followed by an ACK response)
And then (I don't know why):
Rcv TCP END, ACK
Send TCP ACK
Send TCP END, ACK
Rcv TCP ACK
Ok guys, I've got the answer and I think it's a good idea to share it. Actually it's very easy: the trick was just not explicit at all. The callback needNewBodyStream: is called once (or more, see doc) and we just have to bind the NSInputStream object with a NSOutputStream one. Then, data is wrote in the NSOutputStream's callback as and when the "producer" gives new data. Previously, my connection was closed because after the needNewBodyStream call there was nothing to send.
Part of the NSURLSessionTaskDelegate implementation:
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream * bodyStream))completionHandler
{
NSInputStream* inputStream = nil;
NSOutputStream* outputStream = nil;
// Bind inputStream and outputStream
self.inputStream = inputStream;
self.outputStream = outputStream;
[self.outputStream setDelegate:/*NSStreamDelegate implementation*/];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.outputStream open];
completionHandler(self.inputStream);
}
Part of the NSStreamDelegate implementation:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventOpenCompleted:
// to handle...
break;
case NSStreamEventHasSpaceAvailable:
// Producer/Consumer's consumer here, and when data is available:
[self.outputStream write:/*data*/ maxLength:/*data length*/];
break;
case NSStreamEventErrorOccurred:
// to handle...
break;
case NSStreamEventEndEncountered:
// to handle...
break;
default:
break;
}
}
Now you can stream "in live" over HTTP, enjoy :)

How to stream data body after a HTTP request with "Expect: 100-continue" header?

My application streams data over HTTP. It works and I do it like that:
-Initialize and start connection:
NSURLSessionConfiguration* defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession* session = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:self delegateQueue:nil];
NSInputStream* inputStream = [NSInputStream inputStreamWithData:/*data*/];
NSURL* url = /*init from string*/
NSMutableRequest* request : [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = #"POST";
request.HTTPBodyStream = inputStream;
[request addValue:#"no-cache" forHTTPHeaderField:#"Cache-Control"];
[request addValue:#"9999999" forHTTPHeaderField:#"Content-Length"]; // Weird isn't it? But this is in server's specs for live streaming.
NSURLSessionUploadTask* uploadTask = [session uploadTaskWithStreamedRequest:request];
[uploadTask resume];
-Part of the NSURLSessionTaskDelegate implementation:
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream * bodyStream))completionHandler
{
NSInputStream* inputStream = nil;
NSOutputStream* outputStream = nil;
// Bind inputStream and outputStream
self.inputStream = inputStream;
self.outputStream = outputStream;
[self.outputStream setDelegate:/*NSStreamDelegate implementation*/];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.outputStream open];
completionHandler(self.inputStream);
}
-Part of the NSStreamDelegate implementation:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventOpenCompleted:
// to handle...
break;
case NSStreamEventHasSpaceAvailable:
// Producer/Consumer's consumer here, and when data is available:
[self.outputStream write:/*data*/ maxLength:/*data length*/];
break;
case NSStreamEventErrorOccurred:
// to handle...
break;
case NSStreamEventEndEncountered:
// to handle...
break;
default:
break;
}
}
To be precise, there is something I don't show in my code: my connection requires Digest authentication. The URL I used to build my request doesn't contains neither login nor password information. Just after I send the first packet, server send me a 401 so my application performs authentication in didReceiveChallenge: method and send request with auth (Digest here) and data body is continuously sent. All works like a charm!
Now, my problem. Newer server version only responds at the end of the entire request. So when I send my request and my data body (continuously sent) server ignore data and at the end, send me a 401: I can't perform Digest auth. My solution is to add the header "Expect: 100-continue"... And it works (partially, keep reading ;) ) the server responds to me :D... But no data body is transmitted and the connection is closed juste after my app send back the request with Digest auth informations. Just imagine that I add [request addValue:#"100-continue" forHTTPHeaderField:#"Expect"]; in my sample code. There is no call in needNewBodyStream:at all.
Abstract (Wireshark trace):
With my app:
Send TCP request headers without auth
Receive HTTP 401
Send TCP request headers with Digest auth
Close connection.
With curl and the same headers:
Send TCP request headers without auth
Receive HTTP 401
Send TCP request headers with Digest auth
Send data... in continuous until the end...
Close connection.
EDIT:
After my app sends the request with Digest auth, the server doesn't response. I think this is the problem and this is why my connection is closed. But curl sends data anyway and I'd like to do the same. Any idea?
EDIT2:
Should I implements my own NSURLProtocol and remove the Expect: 100-continue header after authentication to make my app able to send data body? Better: is there a way to override the default NSURLProtocol HTTP specs instead of rewriting it from scratch?
WORKAROUND (IMPORTANT):
I make my app works doing a like hack. First, I send a single HTTP (POST) request with neither data nor credentials to force didReceiveChallenge: to be called. In this callback I compute authentication with the NSURLCredentialPersistenceForSession params to bind this information with the session during all its life cycle. Then I cancel this request and create a new one in charge to stream data (exactly like my sample code a the top of this post): Digest auth is included automatically in the first packet sent.
That's a solution to make it works and help people in this case. But it's not the solution I was looking for, so I let this question unresolved and hope for a solution which use the Expect: 100-continue and a single request. Thanks!

iOS multi-threading response

I have an app that requires data from a web server. I have servers in different countries in order to ensure fast response for local request. At the start of the app I would like to decide which server I should use, so my plan is to send a request to different servers and check which responses first.
I have a check.php on each server and return "ok" as response, and I'm able to call it in different threads, but I have no idea how to set serverURL to the server that first response. Can anyone help? Or is there a better way to achieve what I want?
NSURL *url = [NSURL URLWithString:#"http://server_de/check.php"];
NSError *e = nil;
NSString* result = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&e];
if (result == nil || ![result isEqualToString:#"ok"]) {
serverURL=ServerDE;
}
EDIT: all servers will respond, but the ones on the other side of the world will be slower. I need to set the fastest server, not the slowest one.
Let's say you sent a number of requests and every request overrides your serverURL value when it's finished, that's why you get the latest one in the end. To avoid that you could make serverURL as a property or global variable and then check if serverURL has already value before the assignment and if it has, then do not update it.
if ((result == nil || ![result isEqualToString:#"ok"]) && serverURL != nil) {
serverURL = aServer;
}
Actually as soon as you get the first response you can cancel other request because you don't need their answers anymore.

NSURLConnection GET request returns -1005, "the network connection was lost"

I am trying to make a simple GET request using NSURLConnection in XCode 6 (Beta7 2) on iOS 8 SDK, which is failing with "Code 1005, the network connection was lost". The call fails when I try to fetch http://www.google.com or a few other sample pages from the web, but succeeds if I make a request to a simple HTTP server on localhost (python -m SimpleHTTPServer). I have also tried using AFNetworking library (2.4.1) - URLs that fail with NSURLConnection also fail with the library.
Here's my code -
NSString * url = #"http://0.0.0.0:8000";
// NSString * url = #"http://www.google.com";
NSLog(#"URL : %#", url);
// Mutable is probably not required, but just in case it REALLY WANTS me to set HTTP method
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[theRequest setHTTPMethod:#"GET"];
NSURLResponse *urlResponse = nil;
NSError *error = nil;
NSData * data = [NSURLConnection sendSynchronousRequest:theRequest
returningResponse:&urlResponse
error:&error];
if (error == nil) {
NSString *response = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(response);
} else {
NSString *response = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(#"%#", [error userInfo]);
}
Logs:
2014-09-11 17:34:23.950 SearchExample[5092:2074687] URL : http://www.google.com
2014-09-11 17:34:24.023 SearchExample[5092:2074687] {
NSErrorFailingURLKey = "http://www.google.com";
NSErrorFailingURLStringKey = "http://www.google.com";
NSLocalizedDescription = "The network connection was lost.";
NSUnderlyingError = "Error Domain=kCFErrorDomainCFNetwork Code=-1005 \"The network connection was lost.\" UserInfo=0x7fc8515640a0 {NSErrorFailingURLStringKey=http://www.google.com/, NSErrorFailingURLKey=http://www.google.com/, _kCFStreamErrorCodeKey=57, _kCFStreamErrorDomainKey=1, NSLocalizedDescription=The network connection was lost.}";
"_kCFStreamErrorCodeKey" = 57;
"_kCFStreamErrorDomainKey" = 1;
}
2014-09-11 17:34:24.023 SearchExample[5092:2074687] URLResponse: (null)
I have seen internet connectivity failing on the iPhone 6 simulator recently, resulting in the same errors. My Mac had a working internet connection the simulator did not. Restarting the simulator fixed the issue.
I was getting this error consistently on iOS 9 with certain network calls. Two were working fine but another two were not.
It turned out to be caused by some incorrect parameters I was passing with the request's body... I wouldn't have expected that to cause a -1005 error... but it did.. Getting rid of the unnecessary parameters made it all work!
I've tried everything suggested on at least 15 answers from Google but not one of them solved my problem until I tried the following which totally addressed my issue. It appears that Wi-Fi connections can become corrupt on the Mac so if you remove the specific connection you are using and then connect again (by choosing the network and entering your password again) then this will fix the issue and no more dreaded -1005 “the network connection was lost” errors.
Go to the Wi-Fi symbol on your Mac's menubar and "Turn Wi-Fi Off"
Then choose "Open Network Preferences" (from the same menu, at the bottom).
In the bottom right-hand corner of the Network panel, choose "Advanced".
Select the network connection you were previously connected to.
Hit the minus symbol right below this table to delete this connection.
Hit "OK" for this window.
Hit "Apply" on the Network window.
Go back to the Wi-Fi symbol on your menubar and turn Wi-Fi back on.
Wait for your network connection to appear and then select it (and it will now ask for a password again because you deleted the connection info).
It will now remember this newly refreshed connection which should solve the problem!
Try to change request serialization in AFNetworking http or json. in my case that was json then i set to http. Now that is working.
[[VTNetworkingHelper sharedInstance] performRequestWithPath:#"Your url " withAuth:YES forMethod:#"POST" withRequestJSONSerialized:NO withParams:params withCompletionHandler:^(VTNetworkResponse *response) {
if (response.isSuccessful) {
}else {
}];
I have observed this issue occurs when you keep simulator active and your mac goes to sleep for long duration (say 5 to 10 hours). Then all of sudden you run app on simulator the it displays log as
NSURLConnection GET request returns Code=-1005 "The network connection was lost."
The solution is to simply quit simulator, clean project and re-run project.
This worked for me!
I had Similar issue and restarting simulator didn't work. In my case I was able to hit web service alternatively like in odd time it would be successful and in even time it threw me with this error. I know its weird but it was the case somehow. Solved it with following approach in swift :
let urlconfig = NSURLSessionConfiguration.defaultSessionConfiguration()
urlconfig.timeoutIntervalForRequest = 1
urlconfig.timeoutIntervalForResource = 1
let session = NSURLSession(configuration: urlconfig)
let task = session.dataTaskWithRequest(request){(data,response,error) in
//Processing
}
task.resume()
Simple & sample solution, tested many times, working perfect.
//Check response error using status code, and if you get -1005 then call that api again.
if let strErrorReasonCode : Int = response.response?.statusCode {
if authentication_Errors_Range.contains(Alamofire_Error) {
self.POST(urlString, paramaters: paramaters, showLoader: showLoader, success: { (responseObject) in
if response.result.isSuccess {
if let value = response.result.value {
let dictResponce = self.isValidated(value as AnyObject)
if dictResponce.0 == true {
success(dictResponce.1)
}
else {
failure(dictResponce.1)
}
}
}
}, failure: {_ in
failure(jsonResponce)
})
}
}

iOS HTTP request - getting the error response message

This is my bit of code doing a GET request to a REST api.
Im not sure how to get back the message if I get an error:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
NSURL *URL = [NSURL URLWithString:urlString];
[request setURL:URL];
[request setHTTPMethod:#"GET"];
NSError *err = nil;
NSHTTPURLResponse *res = nil;
NSData *retData = [NSURLConnection sendSynchronousRequest:request returningResponse:&res error:&err];
if (err) // This part is never called.
{
NSLog(#"Error: %#", err);
}
else
{
if (res.statusCode != 200)
{
// show the user the status message
NSLog(#"Error: %#", res); // This part is called
}
else
{
}
}
I want to get the error message if it was not successful. But the if (err) block is never called. err is still null, although the statuscode is 400.
And if successful I will get back a json response.
In the code above I get back a statusCode of 400
The error block is not called because the error object is created only if a system level error occurs. This does not happen because the request is sent correctly and the server sends a response. If you are in control of the server, you should probably make it return status code 200 and include an app level status code in the response, that would tell your app that the entered credentials are incorrect.
Edit:
To get status message you can use
+ (NSString *)localizedStringForStatusCode:(NSInteger)statusCode
This is a class method of the NSHTTPURLResponse class.
if (res.statusCode != 200)
{
// show the user the status message
NSLog(#"Error: %#", [NSHTTPURLResponse localizedStringForStatusCode: res.statusCode]); // This part is called
}
Take a look at the NSError class reference:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSError_Class/Reference/Reference.html
You can try to log the error message from the localizedDescription.
you are receiving this status code because- The Web server (running the Web site) thinks that the data stream sent by the client (e.g. your Web browser or our CheckUpDown robot) was 'malformed' i.e. did not respect the HTTP protocol completely. So the Web server was unable to understand the request and process it
to log above problem in respect to ios visit this link
If you read the documentation of sendSynchronousRequest...
error
Out parameter used if an error occurs while processing the request. May be NULL.
this mean that erro will be a valid NSError object in case there is a problem to resolve the request, like a malformed URL.
If the request can be resolved error will be NULL and according with HTTP protocol and depending to the server that you are trying to connect, the NSHTTPURLResponse object will contain all the information about the request.
In general is an error think that every status code different than 200 is an error, for example for a REST based API 204 mean empty data, and in this case the request is finished successfully but the requested resource is just empty data, and this is not an error.
So about your question, is absolutely fine that error is NULL most of the time, if is not mean that there is an issue before reach the target server, in general you have to consider both, error and according to the server that you are trying to talk the status code maps, in most of cases the REST pattern

Resources