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.
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 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.)
I have an application where on a button click i am passing my server api method which calls the JSON post method and saves data to server database.Here i am saving my mobile number and emergency no to server database.My mobile number is in string format.In my mobile number string variable, my mobile number is getting saved which is in this format '+90-9491491411'.I am entering + and then code and then- and then number but when i send to the server database i am removing the - and sending the no to database but problem is in my server database + of mobile is not getting entered which i am entering .What may be the problem .I am using POST method to send the request .This is my code
-(void)sendRequest
{
NSString *newstring = txtMobile.text;
mobileValue = [newstring stringByReplacingOccurrencesOfString:#"-" withString:#""];
NSLog(#"%#",mobileValue);
NSString *newString1 = txtemergencyprovider.text;
emergencyNumber = [newString1 stringByReplacingOccurrencesOfString:#"-" withString:#""];
NSLog(#"%#",emergencyNumber);
if ([txtEmail.text isEqualToString:#""])
{
post = [NSString stringWithFormat:#"CommandType=new&ApplicationType=%d&FullName=%#&Mobile=%#&EmergencymobileNumber=%#&Latitude=%f&Longitude=%f&City=%#&MobileModel=Apple",applicationtype,txtFullname.text,mobileValue,emergencyNumber,latitude,longitude,txtCity.text];
NSLog(#"%#",post);
}
else {
post = [NSString stringWithFormat:#"CommandType=new&ApplicationType=%d&FullName=%#&Mobile=%#&EmergencymobileNumber=%#&Latitude=%f&Longitude=%f&City=%#&EmailAddress=%#&MobileModel=Apple",applicationtype,txtFullname.text,mobileValue,emergencyNumber,latitude,longitude,txtCity.text,txtEmail.text];
NSLog(#"%#",post);
}
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%d", [postData length]];
NSLog(#"%#",postLength);
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"http://myapi?RequestType=NEW"]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (theConnection) {
webData = [[NSMutableData data] retain];
NSLog(#"%#",webData);
}
else
{
}
}
//In my mobile number and emrgency number variable my no is in this format '+91986444711' but when the value is entered in server database + gets removed off .What may be the prob.
Unfortunately, NSString's -stringByAddingPercentEscapesUsingEncoding: will not convert the plus (+) sign into %2B because the plus sign is a valid URL character that is used to separate query parameters. What this usually means is that it gets converted to a space character by the web server.
The easiest way to replace the plus sign would be using NSString's stringByReplacingOccurrencesOfString:withString: to replace the + with %2B. For example:
mobileValue = [mobileValue stringByReplacingOccurrencesOfString:#"+" withString:#"%2B"];
The plus sign ("+") in URL means an encoded space (" ") and most likely your server will interpret it as a space. Change the plus character to %2B in the string before posting it. For a complete solution on URL encoding see this post: http://madebymany.com/blog/url-encoding-an-nsstring-on-ios
The plus sign means space in a post request. You need to convert the plus to a percent escape character. The easiest way to do this is as follows:
NSString* escapedMobileValue = [mobileValue stringByReplacingOccurencesOfString: #"+" withString: #"%2b"];
This will turn the + into %2b. Probably the server will automatically reverse the encoding for you.
(Edited in line with mttrb's comment)
I am trying to use the ASIDownloadCache from the ASIHTTPRequest library. I think I have it almost set up but the data I am printing to the log is a bunch of numbers.. I think it might be a formatting problem.. but I would like to run it past someone with more experience first to make sure I'm doing it correctly and then to hopefully help me fix the issue.
The code belows shows you how I am setting up my cache, I am using this view for several data sets, hence the need to use an if statement so that I am only setting up the cache on specific data.
- (IBAction)setRequestString:(NSString *)string
{
//Set database address
NSMutableString *databaseURL = [[NSMutableString alloc] initWithString:#"http://***.***.***.***:8888/codeData/"]; // 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];
checkDataSet = [[NSString alloc] initWithFormat:string]; //Loads ICMfg.xml into checkDataSet for setting up cache
//Create If statments here
if ([checkDataSet isEqualToString:#"ICMfg.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
[request setDelegate:self]; // A delegate must be specified
[request startSynchronous];
//[request setDidFinishSelector:#selector(requestFinished:)]; // And an appropriate
}
else
{
//this else statments lets all of the other datasets come through here
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
}
From here, when [checkDataSet isEqualToString:#"ICMfg.xml"] is true it will set the cache parameters and then calls the following method where I get everything ready to parse my information
- (void)requestFinished:(ASIHTTPRequest *)request
{
if ([checkDataSet isEqualToString:#"ICMfg.xml"]) {
BOOL success = [request didUseCachedResponse];
NSLog(#"------------>>>>>>> Success is %#\n", (success ? #"YES" : #"NO"));
responseString = [request responseString];
capturedResponseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSLog(#"%#", capturedResponseData); //this prints out the weird data.
[self startTheParsingProcess:capturedResponseData];
}
else
{
responseString = [request responseString]; //Pass requested text from server over to NSString
capturedResponseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
[self startTheParsingProcess:capturedResponseData];
}
}
From here, I check my nslog to see the result of that NSlog and it spits out a bunch of numbers, below is a small section of the output. The next step for me is to check to see if anything is actually being parsed.. and also to see if the cache is working or not.. then I need to figure out hopefully with your help how to format the data correctly if thats my main problem..
also I would like to ask how to get this working asynchronously as currently I can only get it to work synchonosly.
2011-11-09 09:29:55.216 code[3968:207] ------------>>>>>>> Success is YES
2011-11-09 09:29:55.239 code[3968:207] <3c3f786d 6c207665 7273696f 6e3d2231 2e302220 656e636f 64696e67 3d225554 462d3822 3f3e0d0a 3c494345 6e673e3c 52657375 6c742044 42566572 73696f6e 3d223132 33223e3c 5461626c 65733e3c 5461626c 65205461 626c654e 616d653d 2249434d 6667223e 3c526f77 733e3c52 6f77204d 414e5546 41435455 52455249 443d2237 30362220 4d414e55 46414354 55524552 3d22412d 445a4722 2049534c 4f434b4d 414e5546 41435455 5245523d 22462220 49535645 4849434c 453d2246 223e3c2f 526f773e 3c526f77 204d414e 55464143 54555245 5249443d 22333138 22204d41 4e554641 43545552 45523d22 412e522e 452e2220 49534c4f 434b4d41 4e554641 43545552 45523d22 46222049
any help would be greatly appreciated.
I don't see anything that immediately sticks out in your code as wrong.
The NSLog() is printing an NSData object, which is binary data so the hexadecimal numbers you are seeing are the representations of the bytes which is exactly what you would expect.
The NSData Class Reference:
description
Returns an NSString object that contains a hexadecimal
representation of the receiver’s contents.
(NSString *)description
Return Value
An NSString object that contains a hexadecimal representation of the receiver’s contents in
NSData property list format.
If you want to print out the string representation of this data, use:
NSString *capturedResponseString = [NSString stringWithUTF8String:[capturedResponseData bytes]];
I am attempting to implement an online leaderboard in a game app for iOS, using Django to process POST requests from the iDevice and store the scores. I have figured out how to get Django to serialize the objects to XML, and my iPhone can read and display the scores. However, I can't for the life of me get my iPhone to POST XML to my Django server.
Below is the function I am using to post the scores...
iOS (Objective-C) Controller:
- (void) submitHighScore {
NSLog(#"Submitting high score...");
NSString *urlString = HIGH_SCORES_URL;
NSURL *url = [NSURL URLWithString: urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: url];
[request setHTTPMethod: #"POST"];
[request setValue: #"text/xml" forHTTPHeaderField: #"Content-Type"];
NSMutableData *highScoreData = [NSMutableData data];
[highScoreData appendData: [[NSString stringWithFormat: #"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"] dataUsingEncoding: NSUTF8StringEncoding]];
[highScoreData appendData: [[NSString stringWithFormat: #"<player_name>%#</player_name", #"test"] dataUsingEncoding: NSUTF8StringEncoding]];
[highScoreData appendData: [[NSString stringWithFormat: #"<score>%d</score>", 0] dataUsingEncoding: NSUTF8StringEncoding]];
[highScoreData appendData: [[NSString stringWithFormat: #"</xml>"] dataUsingEncoding: NSUTF8StringEncoding]];
[request setHTTPBody: highScoreData];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible: YES];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest: request
delegate: self];
if (!connection) {
NSLog(#"Request to send high scores appears to be invalid.");
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible: NO];
}
}
The above method succeeds in sending the request, and interprets it correctly as CONTENT_TYPE: text/xml, but the Django view that processes the request can't seem to make any sense of it, interpreting it almost as if it was merely plain text. Below is my Django view...
Django (Python) view:
from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.core import serializers
from django.core.exceptions import ValidationError
from django.views.decorators.csrf import csrf_exempt
from modologger.taptap.models import HighScore
#csrf_exempt
def leaderboard( request, xml = False, template_name = 'apps/taptap/leaderboard.html' ):
"""Returns leaderboard."""
if xml == True: # xml is set as True or False in the URLConf, based on the URL requested
if request.method == 'POST':
postdata = request.POST.copy()
print postdata
# here, postdata is evaluated as:
# <QueryDict: {u'<?xml version': [u'"1.0" encoding="UTF-8" ?><player_name>test</player_name<score>0</score></xml>']}>
for deserialized_object in serializers.deserialize('xml', postdata): # this fails, returning a 500 error
try:
deserialized_object.object.full_clean()
except ValidationError, e:
return HttpResponseBadRequest
deserialized_object.save()
else:
high_score_data = serializers.serialize( 'xml', HighScore.objects.all() )
return HttpResponse( high_score_data, mimetype = 'text/xml' )
else:
high_scores = HighScore.objects.all()
return render_to_response( template_name, locals(), context_instance = RequestContext( request ) )
To be honest, I'm not sure whether the problem lies in the Objective-C or in the Django code. Is the Objective-C not sending the XML in the right format? Or is the Django server not processing that XML correctly?
Any insight would be much appreciated. Thanks in advance.
Update:
I got it to work, by editing the iOS Controller to set the HTTPBody of the request like so:
NSMutableData *highScoreData = [NSMutableData data];
[highScoreData appendData: [[NSString stringWithFormat: #"player_name=%#;", #"test"] dataUsingEncoding: NSUTF8StringEncoding]];
[highScoreData appendData: [[NSString stringWithFormat: #"score=%d", 0] dataUsingEncoding: NSUTF8StringEncoding]];
[request setHTTPBody: highScoreData];
For some reason putting a semicolon in there got Django to recognize it, assign the values to a new instance of a HighScore class, and save it. The logging on the test server indicates request.POST is <QueryDict: {u'score': [u'9'], u'player_name': [u'test']}>.
Still not quite sure what to make of all this.
As per Radu's suggestion, I took a look at highScoreData with NSLog, right after appending it to request.HTTPBody, and the result is <706c6179 65725f6e 616d653d 74657374 3b73636f 72653d39>.
I'm a huge Obj-C noob, so again, any help is appreciated! Thanks again.
Since you control both sides, I'd drop the complexity of xml encoding the data and use RestKit or some other framework that makes it easy to communicate with Django.