I am trying to set up a cache, however the method I am using 'as below' is not being accessed by the thread.
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
I am initializing the connection like this, and connectionDidFinishLoading is accessed so I am not sure what I am missing.
- (IBAction)searchRequest:(NSData *)postBodyData
{
//Set database address
NSMutableString *databaseURL = [[NSMutableString alloc] initWithString:#"https://127.0.0.1:88"];
NSURL *url = [NSURL URLWithString:databaseURL];
NSString *postLength = [NSString stringWithFormat:#"%d", [postBodyData length]];
//SynchronousRequest to grab the data, also setting up the cachePolicy
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0]; //if request dose not finish happen within 60 second timeout.
// NSInputStream *fileStream = [NSInputStream inputStreamWithData:postBodyData];
[request setHTTPMethod: #"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/octet-stream" forHTTPHeaderField:#"content-type"];
[request setHTTPBody:postBodyData];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:request delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
receivedData = [NSMutableData data];
} else {
// Inform the user that the connection failed from the connection:didFailWithError method
}
}
any help would be appreciated.
connection:willCacheResponse: is only called in cases when the response will be cached. POST requests are not cacheable in most cases. (More details: Is it possible to cache POST methods in HTTP?)
You should probably look at something like MKNetworkKit which handles a lot of this kind of caching, particularly for REST protocols.
You can also look at Drop-in offline caching for UIWebView. You'd have to modify it significantly, but NSURLProtocol can be used to solve this kind of problem. AFCache is currently working to integrate this approach, and is another toolkit to consider. (Read through the comments in the blog post for more background on the issues.)
Related
I'm developing iPhone app and manually constructing POST requests. Currently, need to compress JSON data before sending it, so looking how to tell a server the content is compressed. Setting content type header to gzip might be not acceptable because server expects JSON data. I'm looking for transparent solution, something like just to add some header telling JSON data is compressed into gzip.
I know, the standard way is to tell the server that the client accepts encoding, but you need to make GET request with accept encoding header first. In my case, I want to post the data already encoded.
Include a Obj-C gzip wrapper, for example NSData+GZip, and use it to encode the body of your NSURLRequest. Also remember to set the Content-Encoding accordingly, so the webserver will know how to treat your request.
NSData *requestBodyData = [yourData gzippedData];
NSString *postLength = [NSString stringWithFormat:#"%d", requestBodyData.length];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"gzip" forHTTPHeaderField:#"Content-Encoding"];
[request setHTTPBody:requestBodyData];
Implmenting some general Method such as follows and setting appropriate Header might help you.
// constructing connection request for url with no local and remote cache data and timeout seconds
NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:[NSURL URLWithString:callingWebAddress]];// cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:timoutseconds];
[request setHTTPMethod:#"POST"];
NSMutableDictionary *headerDictionary = [NSMutableDictionary dictionary];
[headerDictionary setObject:#"application/json, text/javascript" forKey:#"Accept"];
[headerDictionary setObject:#"application/json" forKey:#"Content-Type"];
//Edit as #centurion suggested
[headerDictionary setObject:#"Content-Encoding" forKey:#"gzip"];
[headerDictionary setObject:[NSString stringWithFormat:#"POST /Json/%# HTTP/1.1",method] forKey:#"Request"];
[request setAllHTTPHeaderFields:headerDictionary];
// allocation mem for body data
self.bodyData = [NSMutableData data];
[self appendPostString:[parameter JSONFragment]];
// set post body to request
[request setHTTPBody:bodyData];
NSLog(#"sending data %#",[[[NSString alloc] initWithData:bodyData encoding:NSUTF8StringEncoding]autorelease]);
// create new connection for the request
// schedule this connection to respond to the current run loop with common loop mode.
NSURLConnection *aConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
//[aConnection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
self.requestConnenction = aConnection;
[aConnection release];
I am currently working on an application I need to receive the data in order its very important so instead of going with asynchronous I am using synchronous. However this introduces a very unfortunate side effect, the synchronous request locks up the UI thread.
What I am doing to combat this issue is introduce Multithreading into my app with the use of the life saving "Grand Central Dispatch" services, which seems to be very easy to get my head around so far.
So with all this in mind I am having an issue with what I am doing, Previously I was using asynchronous and everything worked sweet, changing that to synchronous gives me this error
Error Domain=ASIHTTPRequestErrorDomain Code=1 "A connection failure occurred" UserInfo=0x68052a0 {NSUnderlyingError=0x683d250 "The operation couldn’t be completed. Connection refused", NSLocalizedDescription=A connection failure occurred}
Heres my code so far.
- (IBAction)setRequestString:(NSString *)string
{
//Set database address
NSMutableString *databaseURL = [[NSMutableString alloc] initWithString:#"http://192.168.1.1:8778/Data/"]; // iphone development
//PHP file name is being set from the parent view
[databaseURL appendString:string];
//call ASIHTTP delegates (Used to connect to database)
NSURL *url = [NSURL URLWithString:databaseURL];
//Used to Check which ASI cache to use (also used in requestFinished
xmlFileName = [[NSString alloc] initWithFormat:string];
//Set up multithread with GCD
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
//Create If statments here to set up the different caches to be passed down to the next view
if ([string isEqualToString:#"high.xml"]){
//Cache stuff goes in here
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDownloadCache:[ASIDownloadCache sharedCache]];
[request setCacheStoragePolicy:ASICachePermanentlyCacheStoragePolicy];
[request setCachePolicy:ASIOnlyLoadIfNotCachedCachePolicy];
[request setSecondsToCache:60*60*24*30]; // Cache for 30 days - this will change to cache until DBVersion changes
[request setDelegate:self]; // this calls the delegate function requestFinished
dispatch_sync(queue, ^ {
[request startSynchronous];
});
}else if ([string isEqualToString:#"low.xml"]){
//Cache stuff goes in here
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDownloadCache:[ASIDownloadCache sharedCache]];
[request setCacheStoragePolicy:ASICachePermanentlyCacheStoragePolicy];
[request setCachePolicy:ASIOnlyLoadIfNotCachedCachePolicy];
[request setSecondsToCache:60*60*24*30]; // Cache for 30 days - this will change to cache until DBVersion changes
[request setDelegate:self]; // this calls the delegate function requestFinished
dispatch_sync(queue, ^ {
[request startSynchronous];
});
}
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
//.... etc
hopefully that gives you a better idea of what im trying to do, I think maybe I am missing something with the way I am declaring my syncronious start.. as in the asihttprequest help file they say to declare it like this
- (IBAction)grabURL:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSString *response = [request responseString];
}
}
however Im working with data.. so this line
NSString *response = [request responseString];
will not work? dose it need to be NSData.. etc I dunno if someone could help me out that would be great.
You can use nsoperationqueue...
you can create one NSoperationInvoke and add those to NSoperationQueue in order(after reciving data sending another request)...You can add observer to NSOperationQueue to ensure that how many request will process at a time...in your case it will be just one...after receiving the notification in the observer that the synchronous process is completed it will call a selector by performonMainThread for starting another request in the observer itself...
on NSString *response = [request responseString];
issue you can check the request object by [request iskindofClass:[ClassName class]];
or nslog("%#",[request describe]);
it will tell what kind of object request is
Have you considered just adding a serial queue to your code?
dispatch_queue_t queue = dispatch_queue_create("com.myapp", NULL);
You are using a concurrent thread and it's causing multiple operations to occur at the same time. Also, wrap properly your ASIHttpRequest code within the queue blocks.
Give that a try and let us know
In my app I need to download and post some data...
First of all I need to download some data and then I need to do a post request.
I Use async request to don't freeze the UI of my app...
But when I call my method to post some data... I don't care about data returned from server.
But the this method are called also when I do some post request.
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)aResponse
{
NSLog(#"------------------------------- connectionDidReceiveResponse");
expectedResponseLength = [NSNumber numberWithFloat:[aResponse expectedContentLength]];
URLresponse = aResponse;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.responseData appendData:data];
}
How can I do my post request like this below without calling (automatic) this 2 methods (up) (used when I download info) and without freezing user GUI (I don't care data when I do post request but I need data in the 1st case)?
My post request is this:
- (void)postRequestWithURLState:(NSString *)url
{
NSString *bodyRequest = nil;
NSURL *requestURL = [NSURL URLWithString:url];
NSMutableURLRequest *theRequest = [[NSMutableURLRequest alloc] init];
//NSLog(#"-------------- bodyRequest: %#", bodyRequest);
[theRequest setURL:requestURL];
[theRequest setTimeoutInterval:2.0];
[theRequest setHTTPMethod:#"POST"];
[theRequest setHTTPBody:[bodyRequest dataUsingEncoding:NSASCIIStringEncoding]];
[self.oauthAuthentication authorizeRequest:theRequest];
NSURLConnection *conn = [NSURLConnection connectionWithRequest:theRequest delegate:self];
self.web = conn;
}
I was looking around the internet for a solution, I eventually just created my own subclass of NSURLConnection and assigned a tag variable to distinguish. Check out this blog post for more information
You can hold a reference to each of the different requests after performing them, then make some conditional code in the delegate methods that does something different for the two.
That's a rudimentary solution and feels like treating symptoms to me. Maybe you should refactor your approach and create controllers for each of the two operations and perform all of network communication there (it seems like you're doing it all in a view controller now) rather than where you're doing it now.
I need to download three different sets of data from three different URLs. I decided to use ASIHTTPRequest. Two of the URLs are JSON feeds which I need to parse and one of them is a .txt file online that I need to store locally.
Now the example that is on ASIHTTPRequest's website for an asynchronous request shows the following:
- (IBAction)grabURLInBackground:(id)sender {
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
To pass multiple URLs, I can call "request" on three different URLs. But I am not sure how I would handle them in the requestFinished method. The documentation shows it as:
- (void)requestFinished:(ASIHTTPRequest *)request {
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
}
How would this method differentiate between different requests, so that I can handle it differently?
Thank you,
You can differentiate between different requests by
setting the userInfo dictionary of the request
setting the didFinishSelector (and didFailSelector etc.) to different methods
using different classes as delegate
using blocks
using the request's tag property
subclass ASIHTTPRequest and override override requestFinished: and failWithError: (only recommended for complex situations)
You just need two snippets of code. One to 'tag' your url:
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:#"identifierX", #"typeOfRequest",nil]];
and another one to identify it:
if ([[[request userInfo] valueForKey:#"typeOfRequest"] isEqualToString:#"identifierX"]){
// Do here whatever you need to do for the url associated with identifierX
}
and that should be it!
you can set the Username and tag of req.
this is the example of imageview. req.
UIImageView *imgV=[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 416)];
ASIHTTPRequest *req=[ASIHTTPRequest requestWithURL:[NSURL URLWithString:[self.arr objectAtIndex:i]]];
[req setUsername:[NSString stringWithFormat:#"%i",i]];
[req setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:imgV,#"imgV",nil]];
[req setDelegate:self];
[req startAsynchronous];
[imgV setContentMode:UIViewContentModeScaleToFill];
[imgV setClipsToBounds:YES];
[imgV setTag:kTagImageViewInScrollView];
[scr2 addSubview:imgV];
[scr2 setDelegate:self];
[imgV release]; imgV=nil;
and in requestFinished
- (void)requestFinished:(ASIHTTPRequest *)request {
[(UIImageView*)[[request userInfo] valueForKey:#"imgV"] setImage:[UIImage imageWithData:[request responseData]]];
}
You can check the originalURL property of ASIHTTPRequest if you have different URLS.
Or you can use [request hash] to get the NSObject hash for each object and check that later.
I see the rate limit is 150/hr per IP. This'd be fine, but my application is on a mobile phone network (with shared IP addresses).
I'd like to query twitter trends, e.g. GET /trends/1/json.
This doesn't require authorization, however what if the user first authorized with my application using OAuth, then hit the JSON API?
The request is built as follows:
- (void) queryTrends:(NSString *) WOEID {
NSString *urlString = [NSString stringWithFormat:#"http://api.twitter.com/1/trends/%#.json", WOEID];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *theRequest=[NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self startImmediately:YES];
if (theConnection) {
// Create the NSMutableData to hold the received data.
theData = [[NSMutableData data] retain];
} else {
NSLog(#"Connection failed in Query Trends");
}
//NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]];
}
I have no idea how I'd build this request as an authenticated one however, and haven't seen any examples to this effect online.
I've read through the twitter OAuth documentation, but I'm still puzzled as to how it should work.
I've experimented with OAuth using Ben Gottlieb's prebuild library, and calling this in my first viewDidLoad:
OAuthViewController *oAuthVC = [[OAuthViewController alloc]
initWithNibName:#"OAuthTwitterDemoViewController" bundle:[NSBundle mainBundle]];
// [self setViewController:aViewController];
[[self navigationController] pushViewController:oAuthVC animated:YES];
This should store all the keys required in the app's preferences, I just need to know how to build the GET request after authorizing!
Maybe this just isn't possible? Maybe I'll have to proxy the requests through a server side application?
Any insight would be appreciated!
Authorizing through OAuth will provide you an authorization token, which you need to pass to each request you make later on.
Refer to Twitter docs, read about how authorization works.
Okay, after a lot of searching I've managed to figure how to construct a request to the JSON API programmatically in Xcode.
Firstly, you need to use the OAuth demo code to authenticate and authorize your application.
Then, you'll be retrieving the key by doing: [prefs stringForKey:#"authData"] - if this doesn't exist, you haven't been OAuth'd properly.
I had to reverse engineer this by looking through the code of the OAuth library, and while it's easy to use the library for stuff like sending a status update, it doesn't allow you to retrieve trends...:
#import "OAMutableURLRequest.h"
#import "MGTwitterHTTPURLConnection.h"
NSMutableString *dataString;
// Using OAuth:
OAConsumer *consumer = [[OAConsumer alloc] initWithKey:#"YOURCONSUMERKEY"
secret:#"YOURCONSUMERSECRET"];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *authData = [prefs stringForKey:#"authData"];
// [_engine
OAMutableURLRequest *theRequest = [[[OAMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://stream.twitter.com/1/statuses/filter.json"]
consumer:consumer
token: (authData) ? [[OAToken alloc] initWithHTTPResponseBody:authData] : nil
realm: nil
signatureProvider:nil] autorelease];
[theRequest setHTTPMethod:#"POST"];
[theRequest setHTTPBody: [httpBody dataUsingEncoding:NSUTF8StringEncoding]];
[theRequest setHTTPShouldHandleCookies:NO];
// Set headers for client information, for tracking purposes at Twitter.
[theRequest setValue:#"Trendy" forHTTPHeaderField:#"X-Twitter-Client"];
[theRequest setValue:#"1.0" forHTTPHeaderField:#"X-Twitter-Client-Version"];
[theRequest setValue:#"http://www.inisdesign.com" forHTTPHeaderField:#"X-Twitter-Client-URL"];
// Set the request body if this is a POST request.
[theRequest prepare];
// Create a connection using this request, with the default timeout and caching policy,
// and appropriate Twitter request and response types for parsing and error reporting.
MGTwitterHTTPURLConnection *connection;
connection = [[MGTwitterHTTPURLConnection alloc] initWithRequest:theRequest
delegate:self
requestType:MGTwitterFollowedTimelineRequest // Wrong type
responseType:MGTwitterStatuses]; // as above - doesnt seem to matter
if (!connection) {
return;
} else {
// [_connections setObject:connection forKey:[connection identifier]];
// [connection release];
dataString = [[NSMutableData data] retain];
[connection start];
}
}
The rest is implemented as a standard URL connection with didReceiveData methods etc..
I haven't verified this is alleviating my rate limiting problems, but hey.. It's a start if anybody has similar problems.