AFJSONRequestOperation / AFHTTPClient fetching old data - ios

So all I'm trying to do is serve up a .json file somewhere (I've tried both my own personal server, and also on AppFog), then performing a GET request using AFJSONRequestOperation on the iOS platform. My code to do this request is as follows:
AFHTTPClient *aclient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:kBaseURL]];
NSURLRequest *request = [aclient requestWithMethod:#"GET"
path:#"voucher.json"
parameters:nil];
AFJSONRequestOperation *op = [[AFJSONRequestOperation alloc] initWithRequest:request];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"%#", (NSString *)responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[op start];
The problem here is that say the .json file initially contained an array of 2 strings. The above code would fetch these objects just fine. Then if I edited the .json file with another 3 strings, rerunning the code would continue to fetch only the 2 old strings.
Only 10-15 minutes after I made the change to the .json file will a refresh give me the updated data. At first I thought this was a caching issue, but setting [client setCachingPolicy:] didn't make any difference.
This issue is present whether I host my .json file on my static server, or running in a Node express server on AppFog. It just keeps returning an old version of the resource requested.
I'm really puzzled about this: so any help would be greatly appreciated!

Make sure the url request is not cacheing the data. I had to implement the following so I would get new json data each time a fetch was made.
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:5];

This is likely a caching issue - you could change the cache policy on the request or append something onto the query string, like the current time.
To do this you could use something like:
[aclient requestWithMethod:#"GET"
path:[NSString stringWithFormat:#"voucher.json?%#", [NSDate date]]
parameters:nil];

Related

AFNetworking 2.0 and use cache only when offline

I have a problem when users use my app and they lost connection or use airplane mode.
My app server side doesn't set any cache policy and for the time being I can't change it. I migrated from AFNetworking 1.x to 2.0 and now I'm using AFHTTPRequestOperationManager when making requests. The problem is that because I have no cache policy on the server side, every request is made to the server (which for now is fine) but if the user is not able to connect to my server, it doesn't load the cached request.
So, I'm trying the following, using AFHTTPRequestOperation directly like this:
NSURL *URL = [NSURL URLWithString:filePath];
NSMutableURLRequest *request = [[NSURLRequest requestWithURL:URL] mutableCopy];
if (![[AFNetworkReachabilityManager sharedManager] isReachable]) {
[request setCachePolicy:NSURLRequestReturnCacheDataElseLoad];
}
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
[[NSOperationQueue mainQueue] addOperation:op];
This way, if AFNetworkReachabilityManager tells me there's no connectivity, I configured the cache policy for the request and it is loaded from the cache correctly.
The question is, is this the right approach to this situation?
You just need to subclass the AFHTTPSessionManager and check if the client is offline. Then you can change the cache policy or force the app to use cached data.

PUT request with data using AFNetworking 2.0

I want to upload video using PUT request and this is my experience.
I was trying many possibilities of doing PUT request using AFNetworking 2.0, but everything fails.
But I resolved this and this "Question" is information for everybody else who is asking solution.
This works for me:
NSDictionary *headersDict = #{#"Content-Length": [NSString stringWithFormat:#"%#", mySize], #"Content-Type": #"video/mp4", #"Accept": #"application/json", #"Authorization": [NSString stringWithFormat:#"Basic %#", hash]};
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlToPut];
[request setHTTPMethod:#"PUT"];
[request setHTTPBody:videoData];
[request setAllHTTPHeaderFields:headersDict];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// if everything run great, we have to invalidate timer to notify
[uploadTimer invalidate];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
}];
// start timer where app will be checking upload progres on the server
uploadTimer = [NSTimer scheduledTimerWithTimeInterval:.1
target:self
selector:#selector(checkUploadProgressForRecord:)
userInfo:record
repeats:YES];
[operation start];
Anybody has another solution?
You approach has the following issues:
The PUT method is not the suggested method for uploading a file. PUT can be used, but this requires that the server MUST create the resource at the specified URI. In most cases however, the server will create a new URI (which is unknown to the client) when saving the uploaded file locally on the server. In this case, a POST method must be used.
You are using a NSData object which represents the content of the file and assign it the request with setHTTPBody. This is problematic regarding system memory when the file is large and the NSData object uses a heap allocated buffer to hold the file's content. You should either use a NSData object which uses a memory mapped file or a NSStream to represent the file content.
You are too vague about the request headers:
`[request setAllHTTPHeaderFields:headersDict];`
Request header are important. You should set the Content-Type and possible the Content-Length header.
For "Form-based File Upload", see also RFC 1867.

request addValue: forHTTPHeaderField gets the token but does not sends it to server in RestKit

in my app, I have to get a token from my API, do a base 64 encoding with it, and then I have to attach it to all of my api requests.
My Current situation
Using AFNetworking, I set my custom header "Authorization" and send my token with my own base 64 encoding as username:password together,(that is why I am setting it like this is that of using the setAuthenticationwithusername: andPasswordmethod in RestKit). This is how I am making the call.
NSString *TokenAsaHeaderValue =[NSString stringWithFormat:#"Token %#",EncodedToken];
AFHTTPClient *httpClient = [[AFHTTPClient alloc]initWithBaseURL:[NSURL URLWithString:#"http://10.11.5.205:xxxx/"]];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:#"/Games/Current/10" parameters:nil];
[request addValue:TokenAsaHeaderValue forHTTPHeaderField:#"Authorization"];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];
[httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation*operation,id responseObject)
{
self.JSONRESULTS = (NSDictionary *)responseObject;
NSLog(#"print this %#",_JSONRESULTS);
}failure:^(AFHTTPRequestOperation*operation,NSError*error){NSLog(#"Error: %#",error);}];
[operation start];
my problem
I am not sure how to map this. So I want to use RestKit for this call. But when i use nsurlrequest I can only use setValue, which does not adds the header. and when I try to send it as nsmutableurlrequest and add value this is the response I get:
My trial
Using RestKit, I mapped this json in app delegate and then using ObjectManager:
[[RKObjectManager sharedManager]setValue:TokenAsaHeaderValue forKey:#"Authorization"];
and this is what I get as a response:
setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key Authorization.'
(Also, the same mapping was working fine and I was able to get the objects before we implemented the Token request.)
what would also help
Example of how I can map this without RestKit with only AFNetworking.
I appreciate for the help.
setValue:forKey: is the standard KVC method and has nothing to do with setting headers (directly anyway).
RestKit uses AFNetworking under the hood so your configuration that works can be reused by creating your object manager with the client (initWithHTTPClient:) you have or getting the client and configuring it (HTTPClient).

POST Request returning unwanted cached results (AFNetworking)

I'm using AFNetworking to make POST requests from a shared "authenticator" class that passes in a user's username and password. Here is the POST request that I make:
NSURL *url = [NSURL URLWithString:#"https://www..."];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
// params
NSDictionary* dict = #{#"loginName": username,
#"password": password,
#"serviceName": #"...",
#"serviceURL": #"...",
#"action": #"..."};
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
request = [httpClient requestWithMethod:#"POST" path:#"..." parameters:dict];
request.cachePolicy = NSURLRequestReloadIgnoringCacheData;
request.timeoutInterval = 30.0;
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"%#", operation.responseString);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"login failed");
}];
[operation start];
It works very well for the first login and everything returns as expected. When I attempt to login with a different username/password, I see that the output of the operation.responseString is the exact same output as the first login.
Does anyone know why it is returning the output from the first login? I feel that the response is a cached response and I had added the following to try to prevent the return of cached information:
request.cachePolicy = NSURLRequestReloadIgnoringCacheData;
I have set breakpoints to see that the username and password in the NSDictionary for the parameters are the new username/password combination.
The string literals are not manipulated in anyway as well and are the same in every POST request. The elipses are for privacy and are placeholders for strings with semantic meaning.
Try instead
request.cachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData;
because the NSURLRequestReloadIgnoringCacheData only ignores local cache data and not caches out on the network.
Edit: As Steve Madsen points out below, this was not the real problem, and, in general, responses to POST requests are not cached in any case. The actual problem was that the program didn't log out between two logins, by mistake. But we did fix it in the end!
I have same problem, and fixed finally. Using the method:
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0
diskCapacity:0
diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
from this blog post :How Does Caching Work in AFNetworking? : AFImageCache & NSUrlCache Explained
Try it
[request setHTTPShouldHandleCookies:NO];

Posting a serialized object with AFNetworking failing

I have a a data object, called DataElement. It contains a string of Base64 converted image bytes, along with a couple of other fields.
I am trying to post this to my wcf service and am getting an error 'Expected status code in (200-299), got 400.
The goal is to post data + an image to the WCF (rest) service, and get a modified image back- an end to end test of what I am working on.
In my post method, if I leave the encoded string empty on the object everything works just fine- but if that string is anything other than empty I get this error.
My WCF service isn't even being hit, it just bombs right to the error. Here is my post method... what am I doing wrong?
- (void)postDataToServer:(NSString*)server dataElement:(DataElement*)dataElement asJson:(BOOL)useJson
{
NSString *urlString = [[NSString alloc] init];
NSData *encodedData;
urlString = [[server copy] stringByAppendingString:#"EchoXml"];
encodedData = [self encodeDataElementAsXml:dataElement];
NSURL *url = [NSURL URLWithString:urlString];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:urlString parameters:nil];
[request setHTTPBody:encodedData];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[_responseTextView setText:[NSString stringWithFormat:#"Successfully uploaded file to %#", urlString]];
NSObject *httpResponseObject;
httpResponseObject = [self parseResponseAsXml:responseObject];
if ([httpResponseObject isKindOfClass:[DataElement class]])
{
DataElement *dataElement = (DataElement *)httpResponseObject;
_responseTextView.text = dataElement.DataText;
if (dataElement.DataImageBase64 != nil)
{
UIImage *dataImage = [self getImageFromString:dataElement.DataImageBase64];
self.responseImageView.image = dataImage;
}
}
NSLog(#"Successfully uploaded file to %#", urlString);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// It goes here immediately
[_responseTextView setText:[NSString stringWithFormat:#"Error: %#", error]];
NSLog(#"Error: %#", error);
}];
[operation start];
}
Edit: Sorry the formatting got wonky when I pasted it in...
The important parts of your code are:
NSString* urlString = [server stringByAppendingString:#"EchoXml"];
NSURL *url = [NSURL URLWithString:urlString];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSMutableURLRequest *request = [httpClient
requestWithMethod:#"POST" path:urlString parameters:nil];
The actual URL that AFNetorking requests is the AFHTTPClient's base URL, with the specified path appended to it.
Your mistake is that you are specifying the same urlString again.
So, if urlString is http://your.server.com/EchoXml, then the effective URL that you're requesting is http://your.server.com/EchoXmlhttp://your.server.com/EchoXml. As you see, that doesn't work.
Fix your base URL and path to be something more appropriate. Since you didn't say what URL you are trying to access, it's hard to give much more detail. Maybe server should be the base URL, and EchoXml the path?
I know its bad form to answer my own question- but I found and fixed the problem. Bottom line, the code I was using above is fine- maybe not optimal (as Kurt pointed out) but it does what it is supposed to do.
The problem was on on my WCF service- REST service requests by default have a 65k upload limit. I reconfigured the service to allow large file uploads and everything is good.

Resources