Block set by setDataTaskWillCacheResponseBlock on AFHTTPSessionManager is never called - objective-c

I develop an application that relies heavily on AFNetworking to load data from a private API into Mantle models. My API client is a singleton subclass of AFHTTPSessionManager.
I'm trying to implement a basic offline mode by leveraging NSURLCache. The approach is to modify the request's cache policy on dataTaskWithRequest:request:completionHandler to NSURLRequestReturnCacheDataDontLoad if the AFNetworkReachabilityManager says the network is not reachable.
While I can verify that this actually works by using breakpoints during tests, the requests themselves are not being cached. I verified this by downloading the application container from Xcode, and checking the Cache.db sqlite file, which was empty. I also verified that I was looking at the correct cache by manually creating a dummy NSCachedURLResponse and forcefully storing it in the cache using storeCachedResponse:forRequest. Then the dummy response did show up in the Cache.db sqlite file.
So, I think I narrowed down the problem to my "usual" responses not being cached. I can also modify the headers sent by the server API. What I intend to do here is set a header Cache-Control: private on the API responses (so as not to make the other clients that use this API cache responses they shouldn't), and modify this cache header in the block of setDataTaskWillCacheResponseBlock, but this block never fires.
I understand that the URL system may decide when to call URLSession:dataTask:willCacheResponse:completionHandler, which calls the block, based on some undocumented rules, mainly the presence of a Cache-Control header, and the response size/cache size ratio. But my response is a 145-byte JSON, and the Cache-Control header is set (I verified both via curl -v and inspecting the task parameter of the success block of my request.
I also tried a more aggressive cache header, Cache-Control: public, max-age=2592000, to see if this caches the response or at least calls the block, but the behaviour was exactly the same. I also checked the myAFHTTPSessionManagerSubclass.session.delegate property, which was indeed pointing to myAFHTTPSessionManagerSubclass, as expected.
I also tried overriding URLSession:dataTask:willCacheResponse:completionHandler directly in my subclass, but it still was not called. Setting a breakpoint on this same delegate method on AFURLSessionManager.m in the Pods directory also does not work, the execution never stops on the breakpoint.
I'm using version 2.5.4 of AFNetworking, according to my Podfile.lock file.
So, how to make my responses cache (preferably without setting agressive caching policies on the response), so I can implement a quick offline mode in my app?
EDIT: I also tried to create a NSURLSession to see if this would work. So, I created a simple Node.js server that just answers something simple and sets a cache header:
$ curl -v http://192.168.1.107:1337/
* Hostname was NOT found in DNS cache
* Trying 192.168.1.107...
* Connected to 192.168.1.107 (192.168.1.107) port 1337 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: 192.168.1.107:1337
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Cache-Control: public
< Date: Thu, 11 Jun 2015 14:38:14 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
Hello World
And then created a simple view controller to test:
- (void)viewDidLoad {
[super viewDidLoad];
// Prime the cache
[NSURLCache setSharedURLCache:[[NSURLCache alloc] initWithMemoryCapacity:2*1024 diskCapacity:10*1024*1024 diskPath:#"mytestcache"]];
// The sleep is to be absolutely sure the cache did initialise
sleep(2);
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
[[session dataTaskWithURL:[NSURL URLWithString:#"http://192.168.1.107:1337"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Got response: %#", [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
}] resume];
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *))completionHandler {
completionHandler(proposedResponse);
}
The response is correctly printed, but willCacheResponse is never called (I set a breakpoint). I tried checking the container again, and the mytestcache directory was created, but it was empty. I also tried Cache-Control: max-age=86400, to the same result.
( cross-posted to AFNetworking issues at: https://github.com/AFNetworking/AFNetworking/issues/2780 )

Well, I will surprise you - the code you wrote above is working just fine, ...but it's incorrect for what you're trying to achieve ;)
Reason is simply - delegate methods are not being called when you create requests by method dataTaskWithRequest:completionHandler:. You need to use method dataTaskWithRequest: instead.
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/UsingNSURLSession.html#//apple_ref/doc/uid/TP40013509-SW1 - read first Note
Second thing is that NSURLSession just really sux when we talk about caching. You may face several issues in iOS 7.x/8.x. I faced such nightmare when I was implementing custom cache in my app, which eventually has nothing in common with NSURLCache. Also I decided to create my kind of framework based on NSURLSession and AFNetworking.
One of the issues on iOS 7 is creating NSURLSession with defaultConfiguration, the NSURLCache object will be different than global one - quite unexpected behaviour.
One of the issues on iOS 8 is that the POST requests are also being cached, which may compromise security of your app. BTW https://www.google.pl/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=nsurlcache+ios+8+broken
Maybe it does not answer your questions, but you should be aware of that strange behaviours and differences between iOS versions.
Honestly I recommend you to use AFHTTPRequestOperationManager instead of AFHTTPSessionManager, because at least NSURLConnection works like a harm with NSURLCache :)

Related

UTF8 characters come out as windows-1252, ExpressJS running on Electron

A little bit of introduction - the (wannabe) software I'm working on is a desktop application built on Github Electron. It is designed to have two modules - a UI and a backend with an API, the second "living" in a hidden browser window and running on ExpressJS. The idea is for the UI and the API to communicate over HTTP so they could be decoupled if needed. All files are in UTF-8.
Now the problem itself - the main API route looks like:
router.get('/', (request, result) => {
let message = 'Здрасти, коко!';
console.log('Answering with ' + message);
result.json(message);
});
When being called (from a browser, or Postman, or whatever), the answer looks like this:
{"message":"ЗдраÑти, коко!"}
...with those headers:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 64
ETag: W/"40-3JawFDiTNEinvN6xFO6T9g"
Date: Tue, 20 Dec 2016 06:47:53 GMT
Connection: keep-alive
Using the 2cyr tool I found out that the source encoding is UTF-8 but it gets properly displayed only as windows-1252 which confuses me a lot.
To narrow down the possibilities, I added the console.log() to the route handler and (a bit surprising to me) I'm getting the same "broken" result in the Chromium debugger. I suspected the file's encoding, but this is what I get about it:
Petars-Mac:api petar$ file -I api.js
api.js: text/x-c++; charset=utf-8
The last thing that came to my mind was actually decoupling the API from Electron. When I run it in the terminal with node, I actually get the proper result - both in the log message in the terminal and in the JSON answer in the browser.
What am I doing wrong and what further debugging could I possibly do?
So here we go, right before posting an issue in the Electron repository - it's the most stupid error I could imagine ever making in this situation.
TL;DR:
<meta charset="utf-8">
What I thought was that opening a second browser window for the backend and putting some JavaScript that runs in it would be enough. What I forgot was that it actually remains a browser window and therefore it needs just a little tiny bit of HTML to let it know that it serves UTF-8 content.
Maybe it's not me, maybe I was right expecting Express to serve UTF-8 over HTTP but nope. Anyway, it all works now.

AFNetworking downloading a file despite getting 304 Server response

So I want to download a file from a webserver even though the server response is 304 Not Modified.
I use the AFURLSessionManager's downloadTaskWithRequest: progress: destination: completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) method which works fine for files which aren't modified. However if the server response is 304, it seems there is no valid filePath in completionHandler block and the error says "Request failed: not modified (304)".
I tried configurating AFURLSessionManager's requestCachePolicy to NSURLRequestReloadIgnoringLocalAndRemoteCacheData but that doesn't help: although filePath is set, the data can't be reconstructed from this filepath.
Can someone help me by maybe specifying what exactly would be the best practice for downloading a file with AFNetworking and Server response 304. Thanks in advance!
Nevermind I found the problem. I don't have to set the requestCachePolicy of the NSURLSessionConfiguration. I always added IF_MODIFIED_SINCE_HEADER to the request parameter of downloadTaskWithRequest:. Omitting this, it works.

Express.js: how to get assets gzipped

I use compress() middleware, put it the first in configure().
app.configure('all', function(){
app.use(express.compress());
...
app.use(express.static('public'), { maxAge: oneMonth });
})
How do I check that my content is gzipped? I've got a fricking strange situation:
1) On my dev machine: I reqeust localhost:4000/mystyle.css - DON'T see Content-encoding: gzip
2) When I deploy it on production if I request the file it self mydomain.com/mystyle.css - I SEE there see Content-encoding: gzip
3) I request mydomain.com and see in Network in chrome dev tools, find there mystyle.css and there I DON'T see Content-encoding: gzip
4) I use different services to check if my content is gzipped some says that it IS, some that it IS NOT.
WTF? Can some one explain?
Your issue is your use of app.configure. This is largely deprecated, but you're specifically using it such that you're looking for an all environment.
The documentation explains: "This method remains for legacy reason, and is effectively an if statement as illustrated in the following snippets."
Instead, just use the app.use without wrapping them in a configure statement.

NSMutableURLRequest cannot connect to local network

I am using AFNetworking to connect from an iOS v6.0 app to my local Cold Fusion 8 server, not on the same machine but on the same network and the connection times out. When I use an external public server it works fine.
I have tried the connection via IP address and it doesn't work.
I have tried an entry in the hosts file assigning a domain name to the IP address and this doesn't work either. Please see the error below.
I can however connect via a web browser just fine.
Also, the server side files are exactly the same. Versions of Cold Fusion are the same. The only difference that I can find are the public server is Win2003 with IIS6 and the local server is Windows7 with IIS7.
Any thoughts on why this would not work on a local network. Makes local development kind of difficult.
Here is the relevant code:
// load params
NSMutableDictionary *myParams = [[NSMutableDictionary alloc] init];
[myParams setValue:#"Hello" forKey:#"Parameter1"];
[myParams setValue:#"There" forKey:#"Parameter2"];
// Load the base URL into a URL object
// Changing this to an external public server works fine
NSURL *baseURL = [NSURL URLWithString:#"http://www.mylocalmachine.com/"];
// Create HTTP client and init with base url
AFHTTPClient *myHttpClient = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
// POST request to path with the parameters
NSMutableURLRequest *myRequest = [myHttpClient requestWithMethod:#"POST" path:#"myfile.cfm" parameters:myParams];
// Block response from the HTTP client request and pass it back to the calling object
AFJSONRequestOperation *myOperation = [AFJSONRequestOperation JSONRequestOperationWithRequest:myRequest
success:^(NSURLRequest *mySuccessRequest, NSHTTPURLResponse *mySuccessResponse, id mySuccessJSON)
{
// PROCESS THE SUCCESS RESPONSE;
}
failure:^(NSURLRequest *myFailureRequest, NSHTTPURLResponse *myFailureResponse, NSError *myFaliureError, id myFailureJSON)
{
// PROCESS THE FAILURE RESPONSE... AFTER 60 seconds the system will fallout to this block.
}];
// Create a queue object
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
// Add the operation object to the queue
[myQueue addOperation:myOperation];
The following is the error I get print the myFailureError object
Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo=0x8481000 {NSErrorFailingURLStringKey=http://www.mylocalmachine.int/myfile.cfm, NSErrorFailingURLKey=http://www.mylocalmachine.int/myfile.cfm, NSLocalizedDescription=The request timed out., NSUnderlyingError=0x9189e20 "The request timed out."}
Update----
I believe I have narrowed the issue down to what I beleive to be an issue with the iPhone simulator accessing IIS 7. It will access previous versions of IIS on the same network using the same code no problem.
The problem may be in the iPhone Simulator's User-Agent. I have tried to find a way to change the iPhone Simulator's User-Agent or allow the User-Agent in IIS 7 but can not seem to figure it out. The User-Agent the iPhone Simulator 6 is presenting is (iPhone Simulator; ios 6.0; Scale/2.00).
Does anyone know how to either allow this User-Agent on IIS 7 or change the User-Agent in the iPhone simulator?
Has anyone else seen this issue?
Thanks in advance,
Ed
Update----
Hi Everyone
Ok so I figured out how to change the User-Agent and Content-Type using the NSMutableURLRequest. I changed these to match what the browser would send, FireFox browser, and tried again to no avail. I still believe there is an issue with the configuration of IIS 7.5 but I can not find it...
Thanks for anyones help!!!
Ed
please try to remove last 2 lines of code and write
remove this
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
[myQueue addOperation:myOperation];
add this instead
[myoperation start];
and also write failure:nil before that ending square bracket(]) like
AFJSONRequestOperation *myOperation = [AFJSONRequestOperation JSONRequestOperationWithRequest:myRequest
success:^(NSURLRequest *mySuccessRequest, NSHTTPURLResponse *mySuccessResponse, id mySuccessJSON)
{
// PROCESS THE SUCCESS RESPONSE;
}
failure:^(NSURLRequest *myFailureRequest, NSHTTPURLResponse *myFailureResponse, NSError *myFaliureError, id myFailureJSON)
{
// PROCESS THE FAILURE RESPONSE... AFTER 60 seconds the system will fallout to this block.
}failure:nil];
let me know is it working or not...!!!!
Happy Coding!!!!!!
I have resolved this issue. I will post what the resolution was to hopefully help someone else with a similar issue.
Turns out the problem was AVG installed on the system that was running IIS 7. It was a difficult issue to determine because any other system could access the web server with out any problems. It was only specific with the iPhone simulator accessing the system running IIS7. Even Safari or FireFox running on the same Mac would work just fine. Now what within AVG was causing the problem... That is yet to be determined.
I truly hope this helps someone along the way!

Background Intelligent Transfer Service and Amazon S3

I'm using SharpBITS to download file from AmazonS3.
> // Create new download job. BitsJob
> job = this._bitsManager.CreateJob(jobName, JobType.Download);
> // Add file to job.
> job.AddFile(downloadFile.RemoteUrl, downloadFile.LocalDestination);
> // Resume
> job.Resume();
It works for files which do no need authentication. However as soon as I add authentication query string for AmazonS3 file request the response from server is http state 403 -unauthorized. Url works file in browser.
Here is the HTTP request from BIT service:
HEAD /mybucket/6a66aeba-0acf-11df-aff6-7d44dc82f95a-000001/5809b987-0f65-11df-9942-f2c504c2c389/v10/summary.doc?AWSAccessKeyId=AAAAZ5SQ76RPQQAAAAA&Expires=1265489615&Signature=VboaRsOCMWWO7VparK3Z0SWE%2FiQ%3D HTTP/1.1
Accept: */*
Accept-Encoding: identity
User-Agent: Microsoft BITS/7.5
Connection: Keep-Alive
Host: s3.amazonaws.com
The only difference between the one from a web browser is the request type. Firefox makes a GET request and BITS makes a HEAD request. Are there any issues with Amazon S3 HEAD requests and query string authentication?
Regards, Blaz
You are probably right that a proxy is the only way around this. BITS uses the HEAD request to get a content length and decide whether or not it wants to chunk the file download. It then does the GET request to actually retrieve the file - sometimes as a whole if the file is small enough, otherwise with range headers.
If you can use a proxy or some other trick to give it any kind of response to the HEAD request, it should get unstuck. Even if the HEAD request is faked with a fictitious content length, BITS will move on to a GET. You may see duplicate GET requests in a case like this, because if the first GET request returns a content length longer than the original HEAD request, BITS may decide "oh crap, I better chunk this after all."
Given that, I'm kind of surprised it's not smart enough to recover from a 403 error on the HEAD request and still move on to the GET. What is the actual behaviour of the job? Have you tried watching it with bitsadmin /monitor? If the job is sitting in a transient error state, it may do that for around 20 mins and then ultimately recover.
Before beginning a download, BITS sends an HTTP HEAD request to the server in order to figure out the remote file's size, timestamp, etc. This is especially important for BranchCache-based BITS transfers and is the reason why server-side HTTP HEAD support is listed as an HTTP requirement for BITS downloads.
That being said, BITS bypasses the HTTP HEAD request phase, issuing an HTTP GET request right away, if either of the following conditions is true:
The BITS job is configured with the BITS_JOB_PROPERTY_DYNAMIC_CONTENT flag.
BranchCache is disabled AND the BITS job contains a single file.
Workaround (1) is the most appropriate, since it doesn't affect other BITS transfers in the system.
For workaround (2), BranchCache can be disabled through BITS' DisableBranchCache group policy. You'll need to do "gpupdate" from an elevated command prompt after making any Group Policy changes, or it will take ~90 minutes for the changes to take effect.