ASIHttpRequest's post body is null sometimes when building through NSOperation - objective-c

I have a setup where I'm using ASIHttpRequest to make service calls in a background NSOperation subclass. I call a method from my main() in the NSOperation and have that method go through and build out my request with appropriate URL, headers, body, etc. The problem is that I'm occasionally seeing the request body set to null instead of the expected value. I print out what the expected body should be right before setting it to the request and that representation is NOT null, so I don't know where/how the request body for the ASIHttpRequest gets released.
Here's some sample code of how I'm setting the request body ... this method gets called by another driver method that controls the whole networking workflow. That method, in turn, is called by the main() method of my NSOperation.
+ (ASIHTTPRequest*) buildRequestForProjectModify: (ANVideoProject*) theProject {
if (theProject.selfUrl == nil) return nil; //Can't do anything if we don't have a project url.
NSString* projectPutUrl = theProject.selfUrl;
ASIFormDataRequest* request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:projectPutUrl]];
[ANAppsServiceHelper addStandardHeadersToRequest:request];
NSDictionary* projectDict = [theProject jsonFriendlyForSaveAndPreview];
ANLogInfo(#"\n\nProject Post body: \n%#\n\n", projectDict); //This will print out ok
ANLogInfo(#"\n\nProject Post Body as JSON\n%#\n\n", [projectDict JSONRepresentation] ); //this too prints out ok.
NSString* jsonRep = [projectDict JSONRepresentation];
NSData* pd = [jsonRep dataUsingEncoding:NSUTF8StringEncoding];
[request appendPostData:pd];
[request setRequestMethod:#"PUT"];
return request; //I have a problem in the calling method from here where the request body is now null.
}
I read around about setting up an autorelease pool in the main method of my NSOperation subclass, and so I am doing that at the top of the main method. When I did that, the occurrence of (null) for the request body was mitigated, but still happened roughly 1/4 of the time I invoked this operation. Strange thing is that no other part of the request ever seems to get set to null in such a random fashion (ie request method or headers). Here is log output from the driver method I mention above printing out the request that was returned:
Here is Modify the request: https://<service-url>/projects/p0FvVjc790MFWduhhqUStA
Here is the request headers: {
Accept = "application/json";
"Accept-Encoding" = gzip;
Authorization = "Bearer <key>";
"Content-Length" = 293;
"Content-Type" = "application/json";
"User-Agent" = "iPhone (OS 4.3.2)";
}
Here is the request method: PUT
Here is the request body: (null)
Update:
So one thing I'm noticing is that sometimes the request body is null and also sometimes there is some garbage data added on to the end of the request body (and sometimes the request body is totally fine). Even more strangely, the request actually always goes through successfully with the body that I intended to add (even if the printout looks bad). This suggests that things are ok in the ASIHttpRequest but perhaps there is something going on with how I am printing out the state of my request or some other issue between ASIHttpRequest and the SBJSon library I'm using that causes the request to look corrupted from time to time (even though it apparently is not).

Did you print out the content of pd (NSData *)? Is the size very big? Then you may need to stream post data from disk:
[request setRequestMethod:#"PUT"];
[request setPostBodyFilePath:#"/Users/ben/Desktop/another-big-one.txt"];
[request setShouldStreamPostDataFromDisk:YES];

Related

How to get message-body immediately after receive a HTTP header by using NSURLSession?

I want to use NSURLSession to receive a xml stream from server and display each xml immediately on the screen.
Here is my delegate code:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// Enumerate each message-body.
[data enumerateByteRangesUsingBlock:^(const void * _Nonnull bytes, NSRange byteRange, BOOL * _Nonnull stop) {
// Convert message-body to xml string.
NSString *string = [[NSString alloc] initWithBytes:bytes
length:byteRange.length
encoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
// Some code to display string.
// ...
});
}];
}
This code works fine except one problem.
The problem is, sometimes when receiving stream, the didReceiveData doesn't be called immediately after didReceiveResponse, it sometimes receive more than one HTTP messages, and then call didReceiveData once to pass all messages which it just receive for me.
It can sometimes take a while for receiving multiple messages, and makes my application not able to display the xml in realtime.
Is there any configuration or property can make it call didReceiveData immediately? I read the document but find nothing useful.
Thanks a bunch.
Update:
I tried to use NSURLConnection to do the same things, it runs perfectly without this problem.
Each didReceiveData is called behind didReceiveResponse immediately.
How can I make the didReceiveData of NSURLSession work just like NSURLConnection?
IIRC, NSURLSession should send data as it receives it, but only after it receives a certain about of data, or after a period of time.
If you're trying to get individual chunks of data, you might instead consider sending them back from the server as a multipart response. Each "part" would contain one of your messages, and you would get a new didReceiveResponse: callback between each one.
With that said, I'm not sure why NSURLConnection would behave differently. They use a lot of the same code under the hood. You might try filing a bug with Apple.

RestKit PUT not working

I'm trying to do a fairly basic HTTP PUT using RestKit. I don't want to put the entire object, since the API call was designed to accept a single query parameter and just update that field. I've tried two approaches so far, both unsuccessful.
URL to post to: https://myserver/api/users/{userId}
Query string parameter: verificationCode=
Example usage: PUT https://myserver/api/users/101?verificationCode=646133
Approach #1: Put the query parameter in a RKParams object and make the PUT call with those params.
NSString *putUrl = [NSString stringWithFormat:#"/api/users/%i", [APIUserInfo sharedAPIUserInfo].apiUserIdx];
NSLog(#"the PUT url is %#", putUrl);
// Send a PUT to a remote resource. The dictionary will be transparently
// converted into a URL encoded representation and sent along as the request body
NSDictionary* paramsDict = [NSDictionary dictionaryWithObject:[_verificationCode text] forKey:#"verificationCode"];
// Convert the NS Dictionary into Params
RKParams *params = [RKParams paramsWithDictionary:paramsDict];
[[RKClient sharedClient] put:putUrl params:params delegate:self];
Approach #2: Build the entire url and try a PUT with params set to nil.
NSString *putUrl = [NSString stringWithFormat:#"/api/users/%i?verificationCode=%#", [APIUserInfo sharedAPIUserInfo].apiUserIdx, [_verificationCode text]];
NSLog(#"the PUT url is %#", putUrl);
[[RKClient sharedClient] put:putUrl params:nil delegate:self];
Neither approach is working for me. The first fails saying "RestKit was asked to retransmit a new body stream for a request. Possible connection error or authentication challenge?" then runs for about 10 seconds and times out. The second approach fails saying HTTP Status 405 - Method Not Allowed.
Can anyone point out where I'm going wrong, or provide me with a simple PUT example using RestKit? Most of the examples I've found at there are putting the entire object which I don't want to do in this case.
UPDATE:
Approach #2 worked well once I got a few things sorted out on the server side. Final solution:
NSString *putUrl = [NSString stringWithFormat:#"/api/users/verify/%i?verificationCode=%#", [APIUserInfo sharedAPIUserInfo].apiUserIdx, [_verificationCode text]];
NSLog(#"the PUT url is %#", putUrl);
[[RKClient sharedClient] put:putUrl params:nil delegate:self];
the HTTP PUT method is disabled on your webserver. It is by default on all webserver for security reasons.
HTTP Status 405 - Method Not Allowed.

ASIHTTPRequest how to recognize finished request and failed request?

In my application, i have a button wich calls an ASIHTTPRequest. The request goes fine and i receive an response string. But when the request finishes, it always goes to the method: requestFinished. And i also got a requestFailed method. But even when i give a wrong link, the request finsihes and never fails.. Why is this? This is my code:
-(void)fetchForm:(id)sender {
NSURL *URL=[NSURL URLWithString:#"http://www.mydomain.nl/testGet.php"];
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:URL] autorelease];
[request setDelegate:self];
[request setDidFailSelector:#selector(requestFailed:)];
[request startAsynchronous];
}
-(void)requestFinished:(ASIHTTPRequest *)request {
NSLog(#"Request Success!");
}
-(void)requestFailed:(ASIHTTPRequest *)request {
NSLog(#"Request Failed!");
}
EDIT:
I read a little documentation on the ASIHTTPRequest website. And i came to the conclusion that i need to see for myself if there is an error code. i do this with:
int statusCode = [request responseStatusCode];
if (statusCode == 404) {
NSLog(#"Statuscode 404 has occured!");
}
There are several conditions that might affect error reporting with HTTP requests. From ASIHTTP's viewpoint, if the request object can be successfully built, sent, and a some kind of response is received, then everything is ok.
In my case, for example, my ISP has a proxy that will return an error page with many not existing URLs and sometimes even with ill-formed URLs. In such cases, ASIHTTP will not fail. I don't know if this is also your case, but it was for me.
If you look at the file ASIHTTPRequest.m and search for failWithError, you will see all the cases where ASIHTTP will trigger the mechanism that leads to the didFailSelector to be called. You might even set a breakpoint in the failWithError method to see if it is called.
EDIT:
In a sense ASIHTTPRequest mechanism is very basic and covers failures at the network level. If you receive a response then it is an application level failure and you have to deal with it.
First thing is checking the HTTP status code:
int statusCode = [request responseStatusCode];
NSString *statusMessage = [request responseStatusMessage];
This will allow you to identify 404, 500, and so on.
If this does not work and the server does not send an error code, then the only way to go about it is parsing the response you receive and, if it does not contain the data you were waiting for, fail.
try this one -
[request setDidFailSelector:#selector(requestFailed:)];
try this:-
[request setDidFinishSelector:#selector(requestFinished:)];
[request setDidFailSelector:#selector(requestFailed:)];
Write these two lines and then try.

Objective C: Get page content

how to get a web page content from a console application or library? I mean, no UI elemens.
This is what I mean in python
from urllib import urlopen
str = urlopen("http://www.google.com").read()
or php
$str = file_get_contents('http://www.google.com');
or c#
using System.Net;
WebClient client = new WebClient();
string str = client.DownloadString( "http://www.google.com" );
Thanks
NSURLConnection is the class to use in Cocoa, it's usage is pretty straightforward...
Firstly you need to create an instance of NSURLRequest that encompasses the URL you wish to read...
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.stackoverflow.com"]
Create a NSURLConnection to handle your request...
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
Note the second parameter of the init method is a delegate. This delegate needs to implement the following set of methods...
connection:didReceiveResponse:
connection:didReceiveData:
connection:didFailWithError:
connectionDidFinishLoading:
Upon init of the NSURLConnection the download will commence. You can cancel it at any point by send the object a cancel message.
Once you have data to be read the connection will call the connection:didReceiveData: method on it's delegate passing an instance of NSData as the second parameter. This method will be called multiple times as your connection streams you data so use an instance of NSMutableData to aggregate the response...
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[mutableData appendData data];
}
Once the full contents of the URL have been read the connectionDidFinishLoading:(NSURLConnection*) method is invoked. At this point release the connection and use your data.
Check out the documentation for NSURLConnection. This is the asynchronous way to get at it. If you don't mind blocking the thread you can also check out stringWithContentsOfURL:encoding:error: on NSString.
Answer here:
objective c pulling content from website
Just remember to add the framework Webkit

Is it possible to prevent an NSURLRequest from caching data or remove cached data following a request?

On iPhone, I perform a HTTP request using NSURLRequest for a chunk of data. Object allocation spikes and I assign the data accordingly. When I finish with the data, I free it up accordingly - however instruments doesn't show any data to have been freed!
My theory is that by default HTTP requests are cached, however - I don't want my iPhone app to cache this data.
Is there a way to clear this cache after a request or prevent any data from being cached in the first place?
I've tried using all the cache policies documented a little like below:
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
theRequest.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
but nothing seems to free up the memory!
Usually it's easier to create the request like this
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60.0];
Then create the connection
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request
delegate:self];
and implement the connection:willCacheResponse: method on the delegate. Just returning nil should do it.
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
return nil;
}
I have the same problem in my app when I requested info from twitter. In my case I didn't need to preserve those credentials, so I simple erase them using the next code:
- (void) eraseCredentials{
NSURLCredentialStorage *credentialsStorage = [NSURLCredentialStorage sharedCredentialStorage];
NSDictionary *allCredentials = [credentialsStorage allCredentials];
//iterate through all credentials to find the twitter host
for (NSURLProtectionSpace *protectionSpace in allCredentials)
if ([[protectionSpace host] isEqualToString:#"twitter.com"]){
//to get the twitter's credentials
NSDictionary *credentials = [credentialsStorage credentialsForProtectionSpace:protectionSpace];
//iterate through twitter's credentials, and erase them all
for (NSString *credentialKey in credentials)
[credentialsStorage removeCredential:[credentials objectForKey:credentialKey] forProtectionSpace:protectionSpace];
}
}
I hope it works for somebody :)
If you use NSURLConnection take a look at the delegate:
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
Return Value
The actual cached response to store in the cache. The delegate may return cachedResponse unmodified, return a modified cached response, or return nil if no cached response should be stored for the connection.
If you're using NSURLSession, another solution to prevent request and parameters being written to the Cache.db iOS creates within the app's Caches directory, is to set the NSURLCache for the session's configuration to a 0 size memory and 0 size disk cache e.g.
let configuration = URLSessionConfiguration.default
configuration.urlCache = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
let session = URLSession(configuration: configuration)
or as mentioned above set at a global cache level
URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
Presumably it's the 0 for disk size that stops iOS writing to disk but if you have a policy to reloadIgnoringLocalCacheData then you probably aren't interested in memory caching either.
Note This will prevent any Caches/Cache.db (requests & responses) or Caches/fsCachedData/ folder (response data) being created at all. We've decided to take this approach in an app for security purposes as we don't want our requests to be stored on disk cache ever.
If anyone knows is there's a way to stop only request caching but keep response data caching from the iOS URL Loading mechanism, I'd be interested to know. (there's no API or official documentation about this from what I can tell)
If not specific to a single request(U want disable cache for whole app) below one is the best option.Add this code in app delegate or based on ur need any where
int cacheSizeMemory = 0; // 0MB
int cacheSizeDisk = 0; // 0MB
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:#"nsurlcache"];
[NSURLCache setSharedURLCache:sharedCache];
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] url];
[request setValue:#"no-store" forHTTPHeaderField:#"Cache-Control"];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
Assuming the server is correctly implemented, putting the Cache-Control:no-store header in the request will generate a server response with the same header, thus causing NSURLCache to not store the response data on disk.
Therefore, no need for the shotgun approach of disabling NSURLCache disk caching.
PS: Adding the header should work for all HTTP frameworks, like AFNetworking