Setting CURL Arguments in NSURLSession - objective-c

I am trying to execute the following CURL command in a Mac app in Objective-C:
curl -u [USERNAME]:[PASSWORD] -X POST --limit-rate 40000 --header "Content-Type: audio/wav” --header "Transfer-Encoding: chunked" --data-binary #/Users/name/Desktop/test.wav "https://stream.watsonplatform.net/speech-to-text/api/v1/recognize?continuous=true"
I am unsure how to set the various CURL arguments in an NSURLSession. I think I set the HTTP headers correctly, but I don't know how to set the u, limit-rate, and data-binary arguments. This is what I have so far:
NSString* credentials = #"[USERNAME]:[PASSWORD]";
NSString* audioPath = #"#/Users/name/Desktop/test.wav";
NSString* fullPath = #"https://stream.watsonplatform.net/speech-to-text/api/v1/recognize?continuous=true";
NSURL* fullURL = [NSURL URLWithString: fullPath];
NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration
defaultSessionConfiguration];
[sessionConfig setHTTPAdditionalHeaders: #{#"Content-Type" : #"audio/wav",
#"Transfer-Encoding": #"chunked"}];
NSMutableData* body = [[NSMutableData alloc] init];
[body appendData: [[NSString stringWithFormat: #"u: %#", credentials]
dataUsingEncoding: NSUTF8StringEncoding]];
[body appendData: [#"limit-rate: 40000" dataUsingEncoding: NSUTF8StringEncoding]];
[body appendData: [[NSString stringWithFormat: #"data-binary: %#", audioPath]
dataUsingEncoding: NSUTF8StringEncoding]];
NSURLSession* urlSession = [NSURLSession sessionWithConfiguration: sessionConfig];
NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL: fullURL];
[urlRequest setHTTPBody: body];
[urlRequest setHTTPMethod: #"POST"];
NSURLSessionDataTask* dataTask = [urlSession dataTaskWithRequest: urlRequest
completionHandler: ^(NSData* data,
NSURLResponse* response,
NSError* error) {
if (nil == error)
{
if (0 != [data length])
{
// Do something with the returned JSON
}
else
NSLog(#"Data Error: Data is nil or data length is 0.");
}
else
NSLog(#"%#", error);
}];
[dataTask resume];
The error I am getting when I run this code is:
Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}
Does anybody have any ideas? I think it's not working because I'm setting the CURL arguments incorrectly.
Thanks in advance!

The NSURL family of APIs has no mechanism for rate limiting. If that's a requirement, you'll have to use libcurl instead. If not, then:
The body should be the contents of the file (use NSData's dataWithContentsOfURL: method) and nothing else.
The authentication is too complicated to explain here.
One approach is using the techniques described Apple's URL Session Programming Guide, which describes how to do HTTP authentication in depth.
However, the easiest way, by far, to do auth with NSURL* APIs is to just add an Internet password into the keychain, as described in APple's Keychain Services Concepts.

Related

Sending HTTP GET Request using Objective-C

I need to querying some data from Parse API. The code below is the example of CURL from Parse :
curl -X GET \
-H "X-Parse-Application-Id: XXXXX" \
-H "X-Parse-REST-API-Key: XXXXX" \
-G \
--data-urlencode 'where={"key1":"value1","key2":"value2"}' \
https://api.parse.com/1/classes/ClassName
Then, this is my code to achieve that :
NSDictionary *dictionary = #{#"key1": #"value1", #"key1": #"value2"};
NSError *error = nil;
NSString *jsonString = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictionary options:kNilOptions error:&error];
if (!jsonData)
NSLog(#"Got an error: %#", error);
else
jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSString *fullUrl = [NSString stringWithFormat:#"https://api.parse.com/1/classes/ClassName?where=%#", jsonString];
NSURL *url = [NSURL URLWithString:fullUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"GET"];
[request addValue:#"XXXXX" forHTTPHeaderField:#"X-Parse-Application-Id"];
[request addValue:#"XXXXX" forHTTPHeaderField:#"X-Parse-REST-API-Key"];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
NSLog(#"Data: %#", data);
NSLog(#"Response: %#", response);
}else{
NSLog(#"Error: %#", error);
}
}];
[task resume];
After executing, i've got some error :
2015-01-16 18:19:57.532 ParseTest[37964:1018046]
Error: Error Domain=NSURLErrorDomain Code=-1002
"The operation couldn’t be completed. (NSURLErrorDomain error -1002.)"
UserInfo=0x7d17d320 {NSUnderlyingError=0x7d051f70 "The operation couldn’t be completed. (kCFErrorDomainCFNetwork error -1002.)"}
What would be the Objective-C equivalent for accomplishing the past CURL code? Or what do you suggest?
Thanks in advance.
NSURLErrorDomain error -1002 means the URL is unsupported. My guess is that you need to URL encode your json for the where argument. Maybe change making your URL to this:
if (!jsonData)
NSLog(#"Got an error: %#", error);
else
jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
jsonString = [jsonString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *fullUrl = [NSString stringWithFormat:#"https://api.parse.com/1/classes/ClassName?where=%#", jsonString];
If this doesn't work, have you tried logging fullUrl and try to cURL it?
You need to use a PFQuery. Information for that can be found in Parse's iOS/OS X Documentation.
Here's some sample code for doing what you're attempting above. It should be a direct replacement for that cURL code:
- (void)runQuery {
PFQuery *query = [PFQuery queryWithClassName:#"ClassName"];
[query whereKey:#"key1" equalTo:value1];
[query whereKey:#"key2" equalTo:value2];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error != nil) {
// Do error handling here
} else {
// You have a valid query result
}
}
];
}
Just don't forget that you need to initialize your Parse connection in your app before you can make calls. That way, you don't need to pass your keys with each request, as you do in your cURL code. You'd do that in your AppDelegate object's -didFinishLaunchingWithOptions: method, like so:
#import <Parse/Parse.h>
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Initialize Parse.
[Parse
setApplicationId:#"<App Id>"
clientKey:#"<Client Key>"];
}

Bit.ly API Objective C retrieve access_token

I have a quick question.
I want the user to enter his username (email) and password and I want to get an access token from the bit.ly api.
I tried so far:
NSString *authStr = [NSString stringWithFormat:#"%#:%#", #"username", #"password"];
NSData *authData = [authStr dataUsingEncoding:NSUTF8StringEncoding];
NSString *authValue = [NSString stringWithFormat:#"Basic %#", [authData base64Encoding]];
[request setValue:authValue forHTTPHeaderField:#"Authorization"];
However this does not work.
What I need is to get this command of curl to run in objective c:
curl -u "username:password" -X POST "https://api-ssl.bitly.com/oauth/access_token"
I know I can set the method to POST but I am not sure how to set username:password :|
I also tried using
curl -u "CLIENT_ID:CLIENT_SECRET" -d "grant_type=password" -d "username=USERNAME" \
-d "password=PASSWORD" https://api-ssl.bitly.com/oauth/access_token
however I still do not know how ti set the input of client id and client secret.
Any help on how I can set those information into the request would help me much!
Jack
Admittedly, things like AFNetworking do make working with NSURLConnection and the like a bit easier, but if you're doing this from scratch, you're close.
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://api-ssl.bitly.com/oauth/access_token"]];
The endpoint for Resource Owner Credential Grants. That will get you the access_token you need to access the rest of our API.
The trickiest part of the flow is the "Authorization" header
NSString *authString = [NSString stringWithFormat:#"%#:%#", #"<YOUR CLIENT ID>", #"<YOUR CLIENT SECRET>"];
NSData *authData = [authString dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64 = [authData base64EncodedStringWithOptions:0];
NSString *authHeader = [NSString stringWithFormat:#"Basic %#", base64];
[request setValue:authHeader forHTTPHeaderField:#"Authorization"];
This will set the Authorization field to: Basic clientID:clientSecret correctly base 64'ed
From there you need to set the body to the request
NSString *postBody = [NSString stringWithFormat:#"grant_type=password&username=%#&password=%#", #"<USERNAME>", #"<PASSWORD>"];
[request setHTTPBody:[postBody dataUsingEncoding:NSUTF8StringEncoding]];
Tell the request it's POST
[request setHTTPMethod:#"POST"];
Then open a NSURLConnection and observe the 3 important delegated methods
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
You will want to implement:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData*)data
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
Source: I write the Bitly for iPhone app. Hope this helps. Let me know if you have any questions about the API, or the example above.

NSURLConnection closes early on GET

I'm working on a method to centralize my URL connections for sending and receiving JSON data from a server. It works with POST, but not GET. I'm using a Google App Engine server and on my computer it'll handle the POST requests and return proper results (and log appropriately), but I get the following error when I try the request with a GET method:
Error Domain=kCFErrorDomainCFNetwork Code=303 "The operation couldn’t be completed. (kCFErrorDomainCFNetwork error 303.)" UserInfo=0xd57e400 {NSErrorFailingURLKey=http://localhost:8080/api/login, NSErrorFailingURLStringKey=http://localhost:8080/api/login}
In addition, the GAE dev server shows a "broken pipe" error, indicating that the client closed the connection before the server was finished sending all data.
Here's the method:
/* Connects to a given URL and sends JSON data via HTTP request and returns the result of the request as a dict */
- (id) sendRequestToModule:(NSString*) module ofType:(NSString*) type function:(NSString*) func params:(NSDictionary*) params {
NSString *str_params = [NSDictionary dictionaryWithObjectsAndKeys:func, #"function", params, #"params", nil];
NSString *str_url = [NSString stringWithFormat:#"%#%#", lds_url, module];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:str_url]];
NSData *data = [[NSString stringWithFormat:#"action=%#", [str_params JSONString]] dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPMethod:type];
[request setHTTPBody:data];
[request setValue:[NSString stringWithFormat:#"%d", [data length]] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
NSError *error = nil;
NSURLResponse *response = nil;
NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"Error: %#", error);
NSLog(#"Result: %#", [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding]);
return [result objectFromJSONData];
}
A sample call would be:
NSDictionary *response = [fetcher sendRequestToModule:#"login" ofType:#"GET" function:#"validate_email" params:dict];
Again, this works with a POST but not a GET. How can I fix this?
In my case i was not calling [request setHTTPMethod: #"POST" ]
I think the root cause is you have an invalid URL.
JSON encoding will include things like '{', '}', '[' and ']'. All of these need to be URL encoded before being added to a URL.
NSString *query = [NSString stringWithFormat:#"?action=%#", [str_params JSONString]];
query = [query stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:#"%#%#", str_url, query]];
To directly answer your question:
According to CFNetwork Error Codes Reference the error is kCFErrorHTTPParseFailure. This means the client failed to correctly parse the HTTP response.
The reason why is that a GET doesn't include a body. Why would you want to submit JSON in a GET anyways?
If the target api returns data only you pass it in the url params.
If you want to send data and "get" a response use a post and examine the body on return.
Sample Post:
NSError *error;
NSString *urlString = [[NSString alloc] initWithFormat:#"http://%#:%#/XXXX/MVC Controller Method/%#",self.ServerName, self.Port, sessionId ];
NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding ]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
// hydrate the remote object
NSString *returnString = [rdc JSONRepresentation];
NSData *s10 = [returnString dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:s10];
NSURLResponse *theResponse = [[NSURLResponse alloc] init];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&theResponse error:&error];
NSString *message = [[NSString alloc] initWithFormat: #"nothing"];
if (error) {
message = [[NSString alloc] initWithFormat:#"Error: %#", error];
} else {
message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
NSLog(#"%#", message);
return message;

GTM OAuth Twitter update error

I'm having a posting issue with the gtm oauth toolbox. I've been trying to send a tweet to twitter but I keep getting authorization errors. Right now I'm receiving the following error with the code below "POST error: Error Domain=com.google.HTTPStatus Code=401 "The operation couldn’t be completed. (com.google.HTTPStatus error 401.)"
NSString *body = [NSString stringWithFormat: #"status=thisisatest"];
NSString *urlStr = #"http://api.twitter.com/1/statuses/update.json";
NSURL *url = [NSURL URLWithString:urlStr];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody: [body dataUsingEncoding:NSUTF8StringEncoding]];
GTMHTTPFetcher* myFetcher = [GTMHTTPFetcher
fetcherWithRequest:request];
[myFetcher setAuthorizer: mAuth];
[myFetcher beginFetchWithCompletionHandler:^(NSData *retrievedData,
NSError *error)
{
if (error != nil)
{
NSLog(#"POST error: %#", error);
}
else
{
NSDictionary *results = [[[[NSString alloc] initWithData:
retrievedData encoding:NSUTF8StringEncoding] autorelease] JSONValue];
NSLog(#"POST Successful: #%# # %#", [results objectForKey:
#"id"], [results objectForKey: #"created_at"]);
}
}];
What am I doing wrong here? The token is already stored in the keychain. Do I need to retrieve the keychain token or does gtm sign the request automatically?
Ok, I found out the issue. I missed a place in one of the GTM .m files for the consumer key. That's what I get for rushing things. :-)

I'm getting a 500 response code from Tumblr's API v2 on video post

I'm able to successfully submit text posts (with 201 responses) but when I change my type parameter to "video" and add appropriate embed and caption parameters, I get a 500 response from Tubmlr.
Their documentation is pretty light on the details of what I need to give for an embed value, but I've tried linking directly to the file, <iframe>, <embed>, with URL escaping using both methods at the W3Schools, and I always get a 500 response.
From what I've seen on other message boards, this is not uncommon, but no one has an answer. The code I'm using is below.
NSString *apiCallURL = [NSString stringWithFormat:#"http://api.tumblr.com/v2/blog/%#/post", self.baseURL];
NSLog(#"API Call URL: %#", apiCallURL);
NSURL *url = [NSURL URLWithString:apiCallURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setValue:#"utf-8" forHTTPHeaderField:#"charset"];
NSMutableString *body = [[NSMutableString alloc] init]; //Using NSMutableString instead of stringWithFormat to avoid problems with % encoding
[body appendFormat:#"type=video&caption=%#", caption];
[body appendString:#"&embed=EMBED CODE HERE"];
[request setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]];
[body release], body = nil;
[self.authentication authorizeRequest:request];
NSError *error = nil;
NSHTTPURLResponse *response = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error)
{
NSLog(#"Error! \n%#", error);
return;
}
if (data) {
// API fetch succeeded
NSString *str = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
NSLog(#"Sharing response (%d): %#", [response statusCode], str);
}
The output I get is
Sharing response (500):
The embed code I've been trying to use works through tumblr's web interface and is the URL encoded version of the following:
<embed width="480" height="360" src="http://www.keek.com/embed/decaaab" frameborder="0" allowfullscreen />
You'll likely see an answer from the Tumblr devs on the mailing list here:
https://groups.google.com/forum/#!topic/tumblr-api/id_pKWwPcro
It seems to be a problem on tumblr's end.