We are working on an iOS project that involves sending emails through the Gmail API and we are having trouble finding documentation on how to actually do this.
First, we haven't completely figured out authentication. We are using AppAuth to handle that, and it's worked pretty well so far, but we are not quite sure how to link that up to the Gmail API in our code.
Second, how do we send the message itself? We have the content and everything formatted, we just can't figure out how to actually send the message. All we are looking to do is send a simple message to a specified email address from the user's own email account; no attachments or anything like that. We have seen a couple swift examples, however we would prefer to use Objective C. Any ideas on how we could do this?
Update:
After playing around with things a bit more, we found another way to connect to Gmail. Instead of using the classes from the Google API Objective C Client for REST, we are simply trying to send the email using an HTTP POST method. This appears to be way easier than dealing with all of the errors we were getting before. The only problem we have now is that we still can't quite send messages. With nearly everything we've tried, the API just creates an empty message and puts it in our Sent mailbox; that's it. Here's what we have right now:
- (void)sendEmail{
NSURL *userinfoEndpoint = [NSURL URLWithString:#"https://www.googleapis.com/upload/gmail/v1/users/TEST_USERNAME/messages/send?uploadType=media"];
NSString *currentAccessToken = _authState.lastTokenResponse.accessToken;
[self logMessage:#"Trying to authenticate...."];
// Handle refreshing tokens
NSString *message = [NSString stringWithFormat:#"{\"raw\": \"%#\"}",[self generateMessage]];
NSLog(#"%#", message);
// creates request to the userinfo endpoint, with access token in the Authorization header
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:userinfoEndpoint];
NSString *authorizationHeaderValue = [NSString stringWithFormat:#"Bearer %#", accessToken];
[request addValue:authorizationHeaderValue forHTTPHeaderField:#"Authorization"];
[request setHTTPMethod:#"POST"];
[request setValue:#"message/rfc822" forHTTPHeaderField:#"Content-Type"];
[request setValue:[NSString stringWithFormat:#"%lu", (unsigned long)[message length]] forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody:[message dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionConfiguration *configuration =
[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
delegate:nil
delegateQueue:nil];
// performs HTTP request
NSURLSessionDataTask *postDataTask =
[session dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
// Handle response
}];
[postDataTask resume];
}];
}
- (NSString *)generateMessage{
NSString *message = [NSString stringWithFormat:#"From: <TEST_USER#domain.com>\nTo: <TEST_USER#domain.com>\nSubject: Test\n\nThis is a test"];
NSString *rawMessage = [message stringByReplacingOccurrencesOfString:#"\\n" withString:#"\n"];
NSData *encodedMessage = [rawMessage dataUsingEncoding:NSUTF8StringEncoding];
NSString *encoded = [encodedMessage base64EncodedStringWithOptions:0];
NSLog(#"%#", encoded);
return encoded;
}
We have tested the encoding part and it is making a proper base64 string, however after that point, something clearly is not formatted right or something. We get a confirmation that the message was successfully created, however all the API does is create an empty email with no recipient, subject, or body. Any ideas on what we could do to get this to work?
I'm not an expert in this but I remembered we have done something similar in the past. Follow the instructions at the following link and make sure that you select the proper option in Gmail API wizard
https://developers.google.com/gmail/api/quickstart/ios?ver=objc
I hope you find this helpful
After numerous experimentations, here is the code that seems to finally work for me, i worked it off your example above.
1st you need to create google project in dev console, get its Client ID and Api-Key(this may not be necessary) and implement Google SignIn in AppDelegete in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method:
[GIDSignIn sharedInstance].clientID = #"your proj client id here";
[GIDSignIn sharedInstance].delegate = self;
[GIDSignIn sharedInstance].scopes=[NSArray arrayWithObjects:#"https://www.googleapis.com/auth/gmail.send",#"https://www.googleapis.com/auth/gmail.readonly",#"https://www.googleapis.com/auth/gmail.modify", nil];
Now sending emails:
// refresh token
appDelegate.delAuthAccessToken=#"";
[[GIDSignIn sharedInstance] signInSilently];
NSDate *timeStart = [NSDate date];
NSTimeInterval timeSinceStart=0;
while([appDelegate.delAuthAccessToken isEqualToString:#""] && timeSinceStart<10){//wait for new token but no longer than 10s should be enough
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0f]];//1sec increment actually ~0.02s
timeSinceStart = [[NSDate date] timeIntervalSinceDate:timeStart];
}
if (timeSinceStart>=10) {//timed out
return;
}
//compose rfc2822 message AND DO NOT base64 ENCODE IT and DO NOT ADD {raw etc} TOO, put 'To:' 1st, add \r\n between the lines and double that before the actual text message
NSString *message = [NSString stringWithFormat:#"To: %#\r\nFrom: %#\r\nSubject: EzPic2Txt\r\n\r\n%#", appDelegate.delToEmails, appDelegate.delAuthUserEmail, appDelegate.delMessage];
NSURL *userinfoEndpoint = [NSURL URLWithString:#"https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=media"];
NSLog(#"%#", message);
//create request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:userinfoEndpoint];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:[message dataUsingEncoding:NSUTF8StringEncoding]];//message is plain UTF8 string
//add all headers into session config, maybe ok adding to request too
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
configuration.HTTPAdditionalHeaders = #{
#"api-key" : #"api-key here, may not need it though",
#"Authorization" : [NSString stringWithFormat:#"Bearer %#", appDelegate.delAuthAccessToken],
#"Content-type" : #"message/rfc822",
#"Accept" : #"application/json",
#"Content-Length": [NSString stringWithFormat:#"%lu", (unsigned long)[message length]]
};
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
// performs HTTP request
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
// Handle response
}];
[postDataTask resume];
Hope it helps somebody
In my app I used to be able to use MailCore2 but it got blocked by Google (I got access denied when I switched to permitted send, readonly and modify scopes) since MailCore2 works only with FULL permissions. Google allowed to use ONLY send, readonly and modify scopes. There is no guide lines how to use their "great restful api" with Gmail in iOS though, so it seems like HTTP POST is the last resort until they shut it down too.
I cannot have my app to be deemed by Google as insecure. If you are OK with that you can still use MailCore2, no problem.
Receiving email with HTTP GET:
1st get up to 20 unread messages ids:
//get IDs of no more than 20 unread messages
//in query you can add extra filters, say messages only from specific emails
NSString *query=#"from:aaa#gmail.com|from:bbb#yahoo.com";
NSString *tmpStr=[NSString stringWithFormat:#"https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=20&q=\"is:unread\" \"%#\"",query];
NSString *tmpStrURL=[tmpStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *userinfoEndpoint = [NSURL URLWithString:tmpStrURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:userinfoEndpoint];
[request setHTTPMethod:#"GET"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
configuration.HTTPAdditionalHeaders = #{#"api-key" : #"your api key here",
#"Authorization" : [NSString stringWithFormat:#"Bearer %#", yourTokenHere],
#"Accept" : #"application/json"
};
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
// performs HTTP request
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
// Handle response
if (!error){
NSMutableDictionary *jsondata = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
long jsonMsgsCnt = [[jsondata valueForKey:#"resultSizeEstimate"] longValue];
if(jsonMsgsCnt>0){
NSMutableArray *jsonMsgs = [jsondata objectForKey:#"messages"];
for (NSMutableDictionary *tmp in jsonMsgs){
[delMsgsReceived addObject:[tmp objectForKey:#"id"]];
}
}
NSLog(#"retrieve Email Id postDataTask n msg:%li",delMsgsReceived.count);
}else{
NSLog(#"retrieve Email Id postDataTask error:%#",error.description);
}
}];
[postDataTask resume];
Now delMsgsReceived contains messagesIds. Process them to get actual emails one by one:
NSString *tmpStr=[NSString stringWithFormat:#"https://www.googleapis.com/gmail/v1/users/me/messages/%#?format=full", msgId];//supply message id here
NSString *tmpStrURL=[tmpStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *userinfoEndpoint = [NSURL URLWithString:tmpStrURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:userinfoEndpoint];
[request setHTTPMethod:#"GET"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
configuration.HTTPAdditionalHeaders = #{
#"api-key" : #"your api key",
#"Authorization" : [NSString stringWithFormat:#"Bearer %#", your auth token],
#"Accept" : #"application/json"
};
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
// performs HTTP request
NSURLSessionDataTask *postDataTask =
[session dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
// Handle response
if (!error){
NSMutableDictionary *jsondata = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
NSString *body=[jsondata objectForKey:#"snippet"];//not full msg!
//for full message get the whole payload and extract what you need from there NSMutableArray *jsonPayload = [[jsondata objectForKey:#"payload"] objectForKey:#"headers"];
}else{
//deal with error
NSLog(#"retrieving message error:%#",error.description);
}
}];
[postDataTask resume];
Related
I have app iPhone app which will be using API's call.
I made successful call with Username/Password with NSURLSession and receiving token from server....
NSString *username = #"admin";
NSString *password = #"test";
NSString *authString = [NSString stringWithFormat:#"%#:%#",
username,
password];
// 2 - convert authString to an NSData instance
NSData *authData = [authString dataUsingEncoding:NSUTF8StringEncoding];
// 3 - build the header string with base64 encoded data
NSString *authHeader = [NSString stringWithFormat: #"Basic %#",
[authData base64EncodedStringWithOptions:0]];
// 4 - create an NSURLSessionConfiguration instance
NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];
// 5 - add custom headers, including the Authorization header
[sessionConfig setHTTPAdditionalHeaders:#{
#"Accept": #"application/json",
#"Authorization": authHeader
}
];
// 6 - create an NSURLSession instance
NSURLSession *session =
[NSURLSession sessionWithConfiguration:sessionConfig delegate:self
delegateQueue:nil];
// 7 - create an NSURLSessionDataTask instance
NSString *urlString = #"http://test.myserver.am/api/authentication/Login?username=admin&password=test";
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionDataTask *task = [session dataTaskWithURL:url
completionHandler:
^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
if (error)
{
// do something with the error
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200)
{
arrTokenData = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
[self getDomain];
} else {
// failure: do something else on failure
NSLog(#"httpResponse code: %#", [NSString stringWithFormat:#"%ld", (unsigned long)httpResponse.statusCode]);
NSLog(#"httpResponse head: %#", httpResponse.allHeaderFields);
return;
}
}];
// 8 - resume the task
[task resume];
Now I am using token received from server and making another call to get user Data......
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSString *authValue = [arrTokenData valueForKey:#"Token"];
//Configure session with common header fields
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.HTTPAdditionalHeaders = #{#"bearer": authValue};
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSString *url = #"http://test.myserver.am/api/mobile/LookUps/getuserdata";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
if (!error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200)
{
NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:nil];
//Process the data
}
}
}];
[task resume];
but I am receiving below status code and ....request is not getting successful....
httpResponse code: 500
httpResponse head: { "Cache-Control" = "no-cache"; "Content-Length" = 36; "Content-Type" = "application/json; charset=utf-8"; Date = "Thu, 06 Oct 2016 12:16:58 GMT"; Expires = "-1"; Pragma = "no-cache"; Server = "Microsoft-IIS/8.5"; "X-AspNet-Version" = "4.0.30319"; "X-Powered-By" = "ASP.NET"; }
****Please note the same APIs is working fine from another(xamarin APP) platform....**
I am using Objective-C.... IOS10
is there my sending token request is not proper....?
please help me out ..... I am stuck here from yesterday...
As has already been mentioned, I'm pretty sure it should be:
NSString *authValue = [NSString stringWithFormat:#"Bearer %#",
[arrTokenData valueForKey:#"Token"]];
sessionConfiguration.HTTPAdditionalHeaders = #{#"Authorization": authValue};
With that said, a 500 error is an internal server error, not an authentication error. It seems likely that the real problem has nothing to do with authentication, and that your request itself is malformed in some way or that there is a bug in the server-side code that you're somehow tickling.
Also, your code doesn't seem to be checking to see if the token is actually present in the response, unless you're doing that elsewhere.
I would start by checking to make sure the token is actually there, and if it is, enable whatever debugging you can enable on the server and look through the logs to try to figure out what is causing the 500 error on the server side. Chances are, the fix will be obvious once you see what's actually happening on the server side.
Firstly, you should not pass username / password in the ULR in the first authentication call, since you already pass it as a header field. Parameters in the URL are not secure. Sensitive data should always be passed by using POST instead of GET method. But this is not the problem.
Try set the header field in the second call like this:
NSString *authorizationHeader = [NSString stringWithFormat: #"Bearer %#", authValue];
[sessionConfig setHTTPAdditionalHeaders:#{
#"Accept": #"application/json",
#"Authorization": authorizationHeader
}
];
Basically, the auth header field should look like this (from postman):
image
Attention: I did not test / build my code!
Good day.Im requesting to some server and defiantly I'm having some JSON data there so I'm trying to parse it.Im stuck at the point where I'm actually parsing it.So i have method called parseJson which requires NSDictionry as parameter so here how it looks
-(void)parseJson:(NSDictionary*)jsonData{
[jsonData valueForKey:#"email"];
}
as you can see not much here but I'm getting exeption when the code reaches at
[jsonData valueForKey:#"email"];
I have pretty much started developing for iOS from yesterday and the exception is hell as weird for me which is the next.
this class is not key value coding-compliant for the key email.'
So by googling i found nothing...and pretty much in every JSON PARSING tutorial this one line code is written so I'm very much confused what does this exception means....Please help,what am i doing wrong?
FULL REQUEST CODE
-(void) makeRequest{
// Create the request.
__block NSString *returnResponse = #"hello";
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue ]];
//Create an URLRequest
NSURL *url = [NSURL URLWithString:#"http://jsonplaceholder.typicode.com/posts"];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
//Create POST Params and add it to HTTPBody
NSString *params = #"api_key=APIKEY&email=example#example.com&password=password";
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
//Create task
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest: urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(response!=NULL){
returnResponse =[NSString stringWithFormat:#"%#",response];
}else{
returnResponse = [NSString stringWithFormat:#"%#",error.description];
}
[self hideSpinner];
NSString* jsonString = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
[self parseJson:jsonString];
}];
[dataTask resume];
}
Ok i have changed the method now to
-(void)parseJson:(NSString*)jsonData{
NSDictionary * dictionary = [[NSDictionary alloc] initWithContentsOfFile:jsonData];
NSLog(#"%#",jsonData);
NSString* mystring = [dictionary valueForKey:#"email"];
NSLog(#"%#",mystring);
}
and here is the output i get when logging the strings
2016-01-22 00:25:48.690 testproject[627:83537] {
"api_key": "APIKEY",
"email": "example#example.com",
"password": "password",
"id": 101
}
2016-01-22 00:25:48.690 testproject[627:83537] (null)
As you can see the exception problem gone,but now i get NULL value..but you can see that just a one line above i got my son with email key string....so i have fully no clue whats going on.
valueForObject:method has nothing to do with NSDictionary. It is used by KVO which stands for Key-Value Observing. You can retrieve object from NSDictionary by using [] or objectForKey: method. Here are the examples:
dictionary[#"email"]
//or
[dictionary objectForKey:#"email"]
//EDITED
Instead of converting NSObject to NSString and trying accessing properties with KVO or dictionary methods/syntax please try to parse NSData using the code below:
NSDictionary *JSONDictionary =[NSJSONSerialization JSONObjectWithData:data options:0 error:nil]
NSString *email = JSONDictionary[#"email"];
You should be able to retrieve objects from the JSONDictionary using methods/syntax mentioned by me in the first version of the answer.
I need to use NSURLSession to make network calls. On the basis of certain things, after I receive the response, I need to return an NSError object.
I am using semaphores to make the asynchronous call behave synchronously.
The problem is, the err is set properly inside call, but as soon as semaphore ends (after
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
), the err becomes nil.
Please help
Code:
-(NSError*)loginWithEmail:(NSString*)email Password:(NSString*)password
{
NSError __block *err = NULL;
// preparing the URL of login
NSURL *Url = [NSURL URLWithString:urlString];
NSData *PostData = [Post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
// preparing the request object
NSMutableURLRequest *Request = [[NSMutableURLRequest alloc] init];
[Request setURL:Url];
[Request setHTTPMethod:#"POST"];
[Request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[Request setHTTPBody:PostData];
NSMutableDictionary __block *parsedData = NULL; // holds the data after it is parsed
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.TLSMinimumSupportedProtocol = kTLSProtocol11;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
NSURLSessionDataTask *task = [session dataTaskWithRequest:Request completionHandler:^(NSData *data, NSURLResponse *response1, NSError *err){
if(!data)
{
err = [NSError errorWithDomain:#"Connection Timeout" code:200 userInfo:nil];
}
else
{
NSString *formattedData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", formattedData);
if([formattedData rangeOfString:#"<!DOCTYPE"].location != NSNotFound || [formattedData rangeOfString:#"<html"].location != NSNotFound)
{
loginSuccessful = NO;
//*errorr = [NSError errorWithDomain:#"Server Issue" code:201 userInfo:nil];
err = [NSError errorWithDomain:#"Server Issue" code:201 userInfo:nil];
}
else
{
parsedData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&err];
NSMutableDictionary *dict = [parsedData objectForKey:#"User"];
loginSuccessful = YES;
}
dispatch_semaphore_signal(semaphore);
}];
[task resume];
// but have the thread wait until the task is done
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return err;
}
Rob's answer tells you how to do it right, but not what mistake you made:
You have two variables named err, which are totally unrelated. It seems that you haven't turned on some important warnings, otherwise your code wouldn't even have compiled.
The parameter err that is passed to your completion block is the error from the URL request. You replace it without thinking with a timeout error - so the true error is now lost. Consider that timeout is not the only error.
But all the errors that you set only set the local variable err which was passed to you in the completion block; they never touch the variable err in the caller at all.
PS. Several serious errors in your JSON handling. JSON can come in UTF-16 or UTF-32, in which case formattedData will be nil and you incorrectly print "Server Issue". If the data isn't JSON there is no guarantee that it contains DOCTYPE or html, that test is absolute rubbish. Your user with the nickname JoeSmith will hate you.
Passing NSJSONReadingAllowFragments to NSJSONSerialization is nonsense. dict is not mutable; if you try to modify it your app will crash. You don't check that the parser returned a dictionary, you don't check that there is a value for the key "User", and you don't check that the value is a dictionary. That's lots of ways how your app can crash.
I would suggest cutting the Gordian knot: You should not use semaphores to make an asynchronous method behave synchronously. Adopt asynchronous patterns, e.g. use a completion handler:
- (void)loginWithEmail:(NSString *)email password:(NSString*)password completionHandler:(void (^ __nonnull)(NSDictionary *userDictionary, NSError *error))completionHandler
{
NSString *post = ...; // build your `post` here, making sure to percent-escape userid and password if this is x-www-form-urlencoded request
NSURL *url = [NSURL URLWithString:urlString];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
// [request setValue:postLength forHTTPHeaderField:#"Content-Length"]; // not needed to set length ... this is done for you
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"]; // but it is best practice to set the `Content-Type`; use whatever `Content-Type` appropriate for your request
[request setValue:#"text/json" forHTTPHeaderField:#"Accept"]; // and it's also best practice to also inform server of what sort of response you'll accept
[request setHTTPBody:postData];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.TLSMinimumSupportedProtocol = kTLSProtocol11;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *err) {
if (!data) {
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(nil, [NSError errorWithDomain:#"Connection Timeout" code:200 userInfo:nil]);
});
} else {
NSError *parseError;
NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&parseError];
dispatch_async(dispatch_get_main_queue(), ^{
if (parsedData) {
NSDictionary *dict = parsedData[#"User"];
completionHandler(dict, nil);
} else {
completionHandler(nil, [NSError errorWithDomain:#"Server Issue" code:201 userInfo:nil]);
}
});
}
}];
[task resume];
}
And then call it like so:
[self loginWithEmail:userid password:password completionHandler:^(NSDictionary *userDictionary, NSError *error) {
if (error) {
// do whatever you want on error here
} else {
// successful, use `userDictionary` here
}
}];
// but don't do anything reliant on successful login here; put it inside the block above
Note:
I know you're going to object to restoring this back to asynchronous method, but it's a really bad idea to make this synchronous. First it's a horrible UX (the app will freeze and the user won't know if it's really doing something or whether it's dead) and if you're on a slow network you can have all sorts of problems (e.g. the watchdog process can kill your app if you do this at the wrong time).
So, keep this asynchronous. Ideally, show UIActivityIndicatorView before starting asynchronous login, and turn it off in the completionHandler. The completionHandler would also initiate the next step in the process (e.g. performSegueWithIdentifier).
I don't bother testing for HTML content; it is easier to just attempt parse JSON and see if it succeeds or not. You'll also capture a broader array of errors this way.
Personally, I wouldn't return my own error objects. I'd just go ahead and return the error objects the OS gave to me. That way, if the caller had to differentiate between different error codes (e.g. no connection vs server error), you could.
And if you use your own error codes, I'd suggest not varying the domain. The domain should cover a whole category of errors (e.g. perhaps one custom domain for all of your app's own internal errors), not vary from one error to another. It's not good practice to use the domain field for something like error messages. If you want something more descriptive in your NSError object, put the text of the error message inside the userInfo dictionary.
I might suggest method/variable names to conform to Cocoa naming conventions (e.g. classes start with uppercase letter, variables and method names and parameters start with lowercase letter).
There's no need to set Content-Length (that's done for you), but it is good practice to set Content-Type and Accept (though not necessary).
You need to let the compiler know that you will be modifying err. It needs some special handling to preserve that beyond the life of the block. Declare it with __block:
__block NSError *err = NULL;
See Blocks and Variables in Blocks Programming Topics for more details.
I’m trying to use CloudApp’s API (https://github.com/cloudapp/objective-c) to develop my app. I currently want to make it where the user can see the details of their account (e.g.: email, subscription details, etc.). Their API doesn’t seem to work properly to do that sort of thing, but their curl example works perfectly.
curl --digest -u dev2#trijstudios.ca:trij2323 \
-H "Accept: application/json" \
"http://my.cl.ly/account"
Which gets outputted to this:
{"created_at":"2015-01-11T21:08:56Z","domain":null,"domain_home_page":null,"email":"dev2#trijstudios.ca","id":1778166,"private_items":true,"updated_at":"2015-01-11T21:08:56Z","activated_at":"2015-01-11T21:08:56Z","subscribed":false,"socket":{"auth_url":"http://my.cl.ly/pusher/auth","api_key":"4f6dbc3b89fa4ee9a8ff","app_id":"4721","channels":{"items":"private-items_1778166"}},"subscription_expires_at":null}
I wanted to do something as much as the curl statement as possible. I looked around Google and StackOverflow and found this answer (Objective-C equivalent of curl request):
NSURL *url = [NSURL URLWithString: [NSString stringWithFormat:#"http://%#:%##www.example.com/myapi/getdata", API_USERNAME, API_PASSWORD]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setURL:url];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
NSError *error;
NSURLResponse *response;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
I had to slightly modify it, though:
NSString *apiUserName = [NSString stringWithFormat:#"dev2#trijstudios.ca"];
NSString *apiPassword = [NSString stringWithFormat:#"trij2323"];
NSURL *url = [NSURL URLWithString: [NSString stringWithFormat:#"http://%#:%##my.cl.ly/account", apiUserName, apiPassword]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setURL:url];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
NSError *error;
NSURLResponse *response;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"%#", data);
But when I tried it, instead of getting the result of the curl statement in Terminal, I got this in the console:
<3c21646f 63747970 65206874 6d6c3e0a 0a3c6874 6d6c2078 6d6c6e73 3a6f673d 22687474 703a2f2f 6f70656e 67726170 6870726f 746f636f 6c2e6f72 672f7363 68656d61 2f222078 6d6c6e73 3a66623d 22687474 703a2f2f 7777772e 66616365 626f6f6b 2e636f6d 2f323030 382f6662 6d6c2220 6974656d 73636f70 65206974 656d7479 70653d22 68747470 3a2f2f73 6368656d 612e6f72 672f5468 696e6722 20636c61 73733d22 73717561 72657370 6163652d 64616d61 736b2220 6c616e67 3d22656e 2d434122 3e0a0a20 203c6865 61643e0a 20202020 0a202020 203c6d65 74612063 68617273 65743d22 7574662d 38223e0a 20202020 3c6d6574 61206874 74702d65 71756976 3d22582d 55412d43 6f6d7061 7469626c 65222063 6f6e7465 6e743d22 49453d65 6467652c
…
61676522 297c7c68 61734174 74722861 5b625d2c 22646174 612d7372 63222929 26262266 616c7365 22213d3d 67657441 74747228 615b625d 2c226461 74612d6c 6f616422 292b2222 2626496d 6167654c 6f616465 722e6c6f 61642861 5b625d29 7d696e69 7428293b 77696e64 6f772e59 55492626 5955492e 61646428 22737175 61726573 70616365 2d696d61 67656c6f 61646572 222c6675 6e637469 6f6e2861 297b7d29 3b0a7d29 28293b3c 2f736372 6970743e 0a3c7363 72697074 3e537175 61726573 70616365 2e616674 6572426f 64794c6f 61642859 293b3c2f 73637269 70743e0a 0a0a2020 20200a20 203c2f62 6f64793e 0a0a3c2f 68746d6c 3e200a>
(There’s a lot more, but I won’t make it long. Let me know if you need to see the full version.)
I’m not too sure what to do from here. Is there anyone that knows how to fix this? Thanks in advance.
A couple of thoughts
Your response is HTML:
<!doctype html>
<html xmlns:og="http://opengraphprotocol.org/schema/" xmlns:fb="http://www.facebook.com/2008/fbml" itemscope itemtype="http://schema.org/Thing" class="squarespace-damask" lang="en-CA">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,
...
age")||hasAttr(a[b],"data-src"))&&"false"!==getAttr(a[b],"data-load")+""&&ImageLoader.load(a[b])}init();window.YUI&&YUI.add("squarespace-imageloader",function(a){});
})();</script>
<script>Squarespace.afterBodyLoad(Y);</script>
</body>
</html>
This generally means that there was some problem in the request (but without seeing the full text of the HTML, it's hard to say precisely what's wrong).
Your curl specified an Accept header of application/json, but the Objective-C example used that value for the Content-Type header. The request isn't JSON (in this case, at least), so I suspect you meant to set the Accept header in your Objective-C code, as in the curl, not the Content-Type header.
Your curl request specified "digest" authentication. The CloudApp's documentation also says it uses digest authentication. But the Objective-C code is not doing any authentication.
You are performing synchronous network request. You never want to perform synchronous requests from the main thread.
You'll probably want to perform your request using NSURLSession (or if you need to support iOS versions prior to 7.0, NSURLConnection). This solves both points three and four, where you can perform authentication, as well perform the request asynchronously. For example, with NSURLSession, you can use the authentication delegate method, while still enjoying the elegance of the completion block pattern for the request, itself:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
if (challenge.previousFailureCount == 0) {
NSURLCredential *credential = [NSURLCredential credentialWithUser:self.userid password:self.password persistence:NSURLCredentialPersistenceForSession];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
}
// you might have to implement the session rendition of the above authentication routine; it depends upon your server configuration; the implementation will look almost identical to the above code
//
// - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
- (void)performQuery {
// note, no user credentials in the URL; will be handled by authentication delegate method
NSURL *url = [NSURL URLWithString: #"http://www.example.com/myapi/getdata"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!data) {
NSLog(#"dataTaskWithURL error: %#", error);
return;
}
NSError *parseError;
id responseObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if (responseObject) {
// got the JSON I was expecting; go ahead and use it; I'll just log it for now
NSLog(#"responseObject = %#", responseObject);
} else {
// if it wasn't JSON, it's probably some error, so it's sometimes useful to see what the HTML actually says
NSLog(#"responseString = %#", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
}];
[task resume];
}
I am have been trying to send a post request from one of my ViewControllers which collects some data from another ViewController through
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
Anyways when I try to send the post request to send variables to the database, the last two parameter values are stored as 0. I have already converted the float variables to to NSStrings. I checked to make sure the values were correct before sending the post request through NSLog. However it is still showing up as zero. When I enter the form manually through my web browser with the exact same values, they are stored in the database.
My Post request looks like the following:
NSString *dateString = _date.text;
NSString *times = _time.text;
NSString *shape_illuminated = _shape.text;
NSString *orientation_illuminatedString = _orientation.text;
NSLog(#"compass is:%#%#", compassReceived, rollReceived);
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSURL * url = [NSURL URLWithString:#"http://example.com/mobile_app/submit.php"];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
NSString * params = [NSString stringWithFormat:#"date=%#&time=%#&shape_illuminated=%#&orientation_illuminated=%#&=compass_direction=%#&=degreehorizon=%#", dateString, times, shape_illuminated, orientation_illuminatedString, compassReceived, rollReceived];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Response:%# %#\n", response, error);
if(error == nil)
{
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"HttpReponseData = %#",text);
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Account error"
message:[NSString stringWithFormat:#"%#", dictionary]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
}];
[dataTask resume];
Some of the values from those NSLogs are:
compass is:127.0Angle:99.485866
In mysql I have the fields set up as floats.
Any ideas?
UPADATE:
I tried hard coding the values for compass_direction and degreehorizon and it still did not work.
UPDATE:
I have now tried properly configuring the url parameters using the following method:
-(NSString *)urlenc:(NSString *)val
{
CFStringRef safeString =
CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)val,
NULL,
CFSTR("/%&=?$#+-~#<>|,.()[]{}^!"),
kCFStringEncodingUTF8);
return [NSString stringWithFormat:#"%#", safeString];
}
and then adding:
SString *query =
[NSString stringWithFormat:#"shape_illuminated=%#&orientation_illuminated=%#&=compass_direction=%#&=degreehorizon=%#",
[self urlenc:shape_illuminated],
[self urlenc:orientation_illuminatedString],
[self urlenc:convertedCompass],
[self urlenc:convertedRoll]];
NSLog(#"%#", query);
[urlRequest setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"content- type"];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[query dataUsingEncoding:NSUTF8StringEncoding]];
but those two values are still not being sent. The output from the nslog looks fine to me. I even made a php form and entered the same values on my computer which works great, so I do not know what is going on here.