How to send HTTP POST request with gziped content? - iphone

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];

Related

NSURLConnection asynchronous request cuts off body during send

I have a method that periodically sends data to a server and receives a response code back. I implemented NSURLConnection's sendAsynchronousRequest for it, and it was working with small data fine. I've had a chance to test sending base64 encoded pictures, which is a common task for the app, and am finding that it cuts off after a few characters of the encoded image. Here's the code involved.
// Initial setup
NSString *urlStr = #"https://example.com/";
urlRequest = [[NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlStr]] retain];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
[urlRequest setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
// The actual sending part
- (void)sendRequest:(NSString *)reqStr {
#try {
if (sendRequestStatus == kRequestState_waiting) {
sendRequestStatus = kRequestState_processing;
NSString *dataStr = [NSString stringWithFormat:#"request_data=%#",reqStr];
NSLog(#"datastr is %#",dataStr);
NSData *dataForUrl = [dataStr dataUsingEncoding:NSUTF8StringEncoding];
NSLog(#"nsdata length is: %i",[dataForUrl length]);
[urlRequest setHTTPBody:dataForUrl];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *res, NSData *data, NSError *err) {
// housekeeping
}];
}
}
I get no errors in the logs. The log statement that prints out the dataStr prints the correct data. The log statement that prints the length of the data printed out 183384 bytes, which seems appropriate for the picture. On the server however, the post body is cut off a few characters after the start of the encoded image data. An example is /9j/4 where 4 was the last character the server received.
I'm testing this on an iPhone 3Gs. This is a phonegap application, and testing the same on my Android version has no issues, so the problem must be in my implementation of sending the data. I have not heard any problems from another tester using an iPhone 4 or 4s, so it could be an issue of processing speed. What can I do to tighten this up and prevent the post body from ever cutting off?
I didn't test it, but maybe the problem is this:
[urlRequest setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
I would try with this:
[urlRequest setValue:#"text/plain" forHTTPHeaderField:#"Content-Type"];
because you are sending a base 64 encoded string.

cachedResponseForRequest method not being accessed

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.)

Twitter API Rate Limit - Overcoming on an unauthenticated JSON Get with Objective C?

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.

Empty Response with ASIHTTPRequest on WebService

I have some trouble getting a response using ASIHTTPRequest which is quite nerve-wrecking.
I am sending a SOAP Request inside the POST-Body of an ASIHTTPRequest to a server.
The Request is sending fine so far but the Response String is "(null)" according to the Console and the Length of the response Data is 0.
However, when I send the SOAP-Body String, that I put in the Console using NSLog(); to the server with the Firefox Plugin "POSTER" I get the response that i want. And on the other hand, as soon if there'S an error in the SOAP-Request is get a response in ASIHTTPRequest.
So far I'm completely out of ideas and it would be very kind if somebody got an idea to help.
Here'S my Code.
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:myURL];
[request addRequestHeader:#"Content-Type" value:#"text/xml"];
NSLog(#"XML: %#",[carInsurance buildXMLString]); //Debug output
NSMutableData *theData = [[carInsurance buildXMLString] dataUsingEncoding:NSUTF8StringEncoding];
[request addRequestHeader:#"Content-Length" value:[NSString stringWithFormat:#"%d",[theData length]]];
[request setPostBody:theData];
[request setRequestMethod:#"POST"];
NSLog(#"Headers before: %#",[[request requestHeaders] description]); //Debug output
[request startSynchronous];
NSLog(#"Headers after: %#",[[request requestHeaders] description]);
For the more curious one here are my Debug headers.
2010-10-12 14:39:11.945 appname[12484:207] Headers before: {
"Content-Length" = 1944;
"Content-Type" = "text/xml";
}
Install a tool like wireshark and use it to sniff the request and the response (using the iphone simulator).
Presuming the response is empty there too, sniff the request as sent by POSTER and compare that to the one sent by the iphone.
If it's still not obvious what's wrong add the wireshark "follow tcp stream" output into your question.

iphone Json POST request to Django server creates QueryDict within QueryDict

I'm creating a JSON POST request from Objective C using the JSON library like so:
NSMutableURLRequest *request;
request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#/%#/", host, action]]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json-rpc" forHTTPHeaderField:#"Content-Type"];
NSMutableDictionary *requestDictionary = [[NSMutableDictionary alloc] init];
[requestDictionary setObject:[NSString stringWithString:#"12"] forKey:#"foo"];
[requestDictionary setObject:[NSString stringWithString#"*"] forKey:#"bar"];
NSString *theBodyString = requestDictionary.JSONRepresentation;
NSData *theBodyData = [theBodyString dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:theBodyData];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
When I read this request in my Django view the debugger shows it took the entire JSON string and made it the first key of the POST QueryDict:
POST QueryDict: QueryDict: {u'{"foo":"12","bar":"*"}': [u'']}> Error Could not resolve variable
I can read the first key and then reparse using JSON as a hack. But why is the JSON string not getting sent correctly?
This is the way to process a POST request with json data:
def view_example(request):
data=simplejson.loads(request.raw_post_data)
#use the data
response = HttpResponse("OK")
response.status_code = 200
return response
I have already dealt with this issue. I found a temporary solution as reading the request.body dict. I assume you have imported the json/simplejson library already.
In my view:
post = request.body
post = simplejson.loads(post)
foo = post["foo"]
This code block helped me to pass post issue. I think posting querydict in request.POST has not properly developed on NSMutableURLRequest yet.
My cruel hack to work around my problem is:
hack_json_value = request.POST.keys()[0]
hack_query_dict = json.loads(hack_json_value)
foo = hack_query_dict['foo']
bar = hack_query_dict['bar']
So this will allow me to extract the two JSON values with an extra step on server side. But it should work with one step.