I've created an iPhone app for my Arduino, and basically, the Arduino can communicate over the local network using very basic commands provided by a 3rd party REST API. I've successfully been able to use the API via my computer's web browser, but when trying to send a request to it via an iPhone app, it doesn't seem to want to work. Also keep in mind, I can get the API to respond properly via Safari on my iPhone. The only response I'm getting (inside the console) is:
{ URL: http://192.168.0.216/mode/7/0 } { status code: 200, headers {
Connection = close;
"Content-Type" = "application/json";
} } : <7b226d65 73736167 65223a20 2250696e 20443722 6964223a 20223030 38222c20 226e616d 65223a20 226d6967 6874795f 63617422 2c202263 6f6e6e65 63746564 223a2074 7275657d 0d0a>
The API is indeed supposed to return JSON data, but the response on the web browser actually affects my Arduino's LED.
Code for Turning the LED on
NSURL *modeSet = [NSURL URLWithString:[NSString stringWithFormat:#"http://192.168.0.216/digital/%d/1", _pin]];
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:modeSet
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSLog([NSString stringWithFormat:#"%# : %#", response, data]);
}] resume];
EDIT: I decided to print out the 'Error' variable to see if it was holding anything back from me, and I found this:
Error Domain=NSURLErrorDomain Code=-1001 "The operation couldn’t be completed.
(NSURLErrorDomain error -1001.)" UserInfo=0x17807b840 {NSErrorFailingURLStringKey=http://192.168.0.216/mode/7/o,
NSUnderlyingError=0x178449450 "The operation couldn’t be completed.
(kCFErrorDomainCFNetwork error -1001.)", NSErrorFailingURLKey=http://192.168.0.216/mode/7/o}
Pre-iOS 9 Answer
Answering my own question so if anyone finds this by Google sometime, they won't have to ask.
All I did was formatted my string correctly with NSUTF8Encoding like so:
NSString *modeSetString = [[NSString stringWithFormat:#"http://192.168.0.216/mode/%d/o", _pin] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *modeSet = [NSURL URLWithString:modeSetString];
iOS 9 Update
stringByReplacingPercentEscapesUsingEncoding: is now deprecated and stringByRemovingPercentEncoding should be used instead like so:
NSString *modeSetString = [[NSString stringWithFormat:#"http://192.168.0.216/mode/%d/o", _pin] stringByRemovingPercentEncoding];
NSURL *modeSet = [NSURL URLWithString:modeSetString];
Related
I submitted an app to the store which was subsequently rejected, as it connects to an external server to load a json feed, which runs on IPv4. There are two separate internet connections where I work. The app successfully loads the json feed on one of the connections, but returns a 404 not found error on the other. Obviously when the app was in review it must have returned a 404 error. I am using a NSURLSession to connect to the api, as I understand it this is able to handle IPv4 to IPv6 mapping. What other method can I use to prevent this 404 not found error? The following is a snippet of my code:
NSURLSession *session = [NSURLSession sharedSession];
NSLog(#"%#", session.configuration);
[[session dataTaskWithURL:[NSURL URLWithString:jSONURLString]
completionHandler:^(NSData *rawData,
NSURLResponse *response,
NSError *error) {
if ((rawData != nil) && (error == nil)) {
//NSLog(#"Data: %#", rawData);
//NSLog(#"%#",response);
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelector:#selector(returnRawData:) withObject:rawData];
});
}
else {
NSLog(#"error...");
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelector:#selector(noFeedReturned)];
});
}
// handle response
}] resume];
Solution was to reconfigure the server to be compatible with both IPv4 and IPv6
I'm trying to create an iOS app that uses OAuth2 authentication using the native iOS NSURLSession URL loading classes. I gain an access token fine using the directions here:
http://www.freesound.org/docs/api/authentication.html
I subsequently launch the application and run a search query
https://www.freesound.org/apiv2/search/text/?query=snare
The request header fields looks like this (note my access token is not expired and I have confirmed it is the same as I received from performing the steps above)
{
"Authorization: Bearer" = MY_ACCESS_TOKEN;
}
This fails with:
{"detail": "Authentication credentials were not provided."}
The response headers look like this:
{
Allow = "GET, HEAD, OPTIONS";
Connection = "keep-alive";
"Content-Type" = "application/json";
Date = "Sat, 31 Jan 2015 13:56:32 GMT";
Server = "nginx/1.2.1";
"Transfer-Encoding" = Identity;
Vary = "Accept, Cookie";
"Www-Authenticate" = "Bearer realm=\"api\"";
}
The funny thing is that this does not always happen. If I repeat this entire process a number of times, deleting the app in between, it will eventually work. Once it works, it will continue to work while I'm developing. Sometimes then when I come back to it, say the next day, it stops working and I need to repeat this deleting and re-installing routine to get it back working again!
There's an authentication challenge delegate method on NSURLSession that will get called if implemented. It's a 'server trust' challenge. Could this be something to do with it? Would you even expect an authentication challenge of this nature? There's nothing mentioned about it in the docs alluded to above.
Any help would be much appreciated.
EDIT
This is how the search text ("snare") GET call is made.
I basically pass in an NSMutableURLRequest with the URL set to the above (https://www.freesound.org/apiv2/search/text/?query=snare). useAccessToken is set to YES.
- (void)makeRequest:(NSMutableURLRequest *)request useAccessToken:(BOOL)useAccessToken completion:(CompletionBlock)completion {
NSAssert(completion, #"No completion block.");
if (useAccessToken) {
NSString *accessToken = [[ODMFreesoundTokenCache sharedCache] accessToken];
NSAssert(accessToken.length, #"No access token.");
[request addValue:accessToken forHTTPHeaderField:#"Authorization: Bearer"];
}
NSLog(#"Making request: %# \n\nWith access token: %#", request, [[ODMFreesoundTokenCache sharedCache] accessToken]);
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSInteger code = [(NSHTTPURLResponse *)response statusCode];
if (code == 200) {
if (!error) {
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"json: %#", json);
completion(json, error);
}
else {
completion(nil, error);
}
}
else {
NSString *reason = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSError *error = [NSError errorWithDomain:#"Request Error" code:code userInfo: reason ? #{NSLocalizedDescriptionKey : reason} : nil];
NSLog(#"error: %#", error);
completion(nil, error);
}
}];
[task resume];
}
The 2 flows for authentication described in the doc are not "safe" for a device. Using API keys would require the secret to be stored in the device.
The OAuth2 flow they support (authorization_code) requires a server to server call to exchange a code for the actual token (This step: http://www.freesound.org/docs/api/authentication.html#step-3). This call requires another credential (the client_secret that you probably should not store in the device either.
You need a server in between that negotiates this for you. Or a server that translates the code flow into token one. (Illustrated here: https://auth0.com/docs/protocols#5).
I need to get image information from server, such image name, image id. Then use image id as one of parameters to make post, get image actual data. More specific, there are three images I should get.
First, I use getImageInfo to get image information.
- (void)getImageInfo {
// compose request
NSUserDefaults *getUserInfo = [NSUserDefaults standardUserDefaults];
NSString *uid = [getUserInfo objectForKey:#"uid"];
NSString *checkCode = [getUserInfo objectForKey:#"checkCode"];
NSString *data = [NSString stringWithFormat:#"uid=%#&yangzhengma=%#", uid, checkCode];
NSURL *url = [NSURL URLWithString:#"http://121.199.35.173:8080/xihuan22dcloud/services/Shibietupianservice/serviceGetallshibietu"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPBody = [data dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPMethod = #"POST";
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
if (!error) {
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
// parse data in ram and put into images' imageInfos array
[self.images parseImageInfo:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
[self getImageRawData];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}
}] resume];}
Then I use getImageRawData to get three image data.
- (void)getImageRawData {
// compose request dynamically
NSUserDefaults *getUserInfo = [NSUserDefaults standardUserDefaults];
NSString *uid = [getUserInfo objectForKey:#"uid"];
NSString *checkCode = [getUserInfo objectForKey:#"checkCode"];
NSURL *url = [NSURL URLWithString:#"http://121.199.35.173:8080/xihuan22dcloud/services/Shibietupianservice/serviceGetthetupian"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = #"POST";
NSInteger count = 0;
for (ImageInformation *temp in self.images.imageInfos) {
NSString *data = [NSString stringWithFormat:#"uid=%#&yangzhengma=%#&tupianid=%#", uid, checkCode, temp.imageId];
request.HTTPBody = [data dataUsingEncoding:NSUTF8StringEncoding];[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
// if client side is no errors, continue
if (!error) {
// if server side is no errors, continue
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
NSLog(#"图片内容:%#", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
// in ram and put into images' imageRawData array
[self.images parseImageRawData:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] withImageId:temp.imageId withIndex:count];
// store data to disk
// NSString *path = [[NSString alloc] initWithFormat:#"image%#", temp.imageId];
// [FCFileManager writeFileAtPath:path content:data];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}
}] resume];
count++;
}}
Here, it will loop three times, three responses come back, only the last one is complete, the others carry a error message, or incomplete raw data sometimes. Now I'm diving into concurrency programming guide, I guess serial queue likely can solve this problem.
Output like this:
2014-12-16 22:38:48.739 WeddingNewVersion[997:83366] 图片内容:<ns:serviceGetthetupianResponse xmlns:ns="http://serviceimpl.my.com"><ns:return>error</ns:return></ns:serviceGetthetupianResponse>
2014-12-16 22:38:48.749 WeddingNewVersion[997:83366] 图片内容:<ns:serviceGetthetupianResponse xmlns:ns="http://serviceimpl.my.com"><ns:return>error</ns:return></ns:serviceGetthetupianResponse>
2014-12-16 22:38:51.943 WeddingNewVersion[997:83366] 图片内容:<ns:serviceGetthetupianResponse xmlns:ns="http://serviceimpl.my.com"><ns:return>/9j/...(complete data)...9k=%%226654474.0</ns:return></ns:serviceGetthetupianResponse>
parameters of requests:
2014-12-17 14:59:25.364 WeddingNewVersion[1875:226651] uid=6&yangzhengma=odWoDXWcBv1jOrEhywkq7L&tupianid=41
2014-12-17 14:59:25.368 WeddingNewVersion[1875:226651] uid=6&yangzhengma=odWoDXWcBv1jOrEhywkq7L&tupianid=42
2014-12-17 14:59:25.368 WeddingNewVersion[1875:226651] uid=6&yangzhengma=odWoDXWcBv1jOrEhywkq7L&tupianid=43
the problem is likely not in composing request.
------------------------------------------------update1-----------------------------------------------
I have tried to put data task of session into a serial queue. Disappointed, this is not working.
dispatch_async(self.serialQueue, ^{
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){...}] resume];
});
Meanwhile, I make delegateQueue of session as nil, reference says if nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.
Now I am still confused how to make it right.
-----------------------------------------------update2------------------------------------------------
I add [NSThread sleepForTimeInterval:0.5] into the block dispatched to serial queue.
dispatch_async(self.serialQueue, ^{
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){...}] resume];
[NSThread sleepForTimeInterval:0.5];
});
It does not work. The three responses are complete, but they are all the same.
Thank you in advance!
I'm just guessing as I've never tried it, but possibly your data tasks are all using the same TCP port on your end.
That would be OK if they were serialized - one after the other, in sequence - but if they overlap, then the server would receive garbled HTTP requests:
GET /foo
GET /bar
GET /baz
What the server would see might be something like:
GET /fGET /baroo
GET /baz
That your third requests actually works OK might be an accident of the timing.
If you absolutely require the three requests to be issued simultaneously, there are ways to open three different ports on your end. I don't know how to do it with Cocoa and Objective-C, but you can certainly do it with C and Berkeley Socket system calls. The Cocoa / Cocoa Touch networking methods are just wrappers around sockets.
A couple of thoughts:
Your technique of using a single NSMutableURLRequest instance, and repeatedly mutating it for each request (while the prior requests are still in progress) is curious.
In the spirit of thread safety, I would use a separate NSMutableURLRequest for each concurrent request. You don't want to risk having your thread issuing these requests mutate the request object while some background thread performing one of the prior requests. (See Apple's Thread Safety Summary in the Threading Programming Guide in which they point out that mutable classes are not generally thread safe.)
Having said that, the NSURLConnection documentation leaves us with the impression that this request object would be copied, mitigating this problem. I don't see this sort of assurance in the NSURLSession documentation (though I suspect it does the same thing).
I don't think this is the problem here (if this was the problem, the problem would likely be more erratic than what you report, and besides, I suspect that NSURLSession is handling this gracefully, anyway), but as a matter of good thread-safe coding habits, it would be prudent to let each concurrent request have its own NSMutableURLRequest object.
You have confirmed that the information being used in the requests looks valid.
If you wanted to take this to the next level, you might use Charles (or Wire Shark or whatever tool you prefer) to observe the actual requests as they go out. These sorts of tools are invaluable for debugging these sorts of problems.
If you observe the requests in Charles and confirm that they are valid, then this categorically eliminates client-side issues from the situation.
What is curious is that you are not receiving NSError object from dataTaskWithRequest. Nor are you receiving statusCode other than 200 from your server. That means that your requests were successfully sent to the server and received by the server.
Instead, the server is processing the request, but is having a problem fulfilling the request. This leads me to wonder about the server code, itself. I suspect that there is something in the server code that is preventing concurrent operations from taking place (e.g., locking some shared resource, such as temp file or SQL table, for the duration of the request). I would take a hard look at the server code and make sure there are no potential contention issues.
Furthermore, I would modify the server code to not simply report "error", but rather to produce a meaningful error message (e.g. system provided error messages, error codes, etc.). Your server is detecting an error, so you should have it tell you precisely what that error was.
Note, I am explicitly not advising you to make your requests run sequentially. That is inadvisable. While it might solve the immediate problem, you pay a huge performance penalty doing that, and it's not scalable. And remember, you really must handle concurrent requests gracefully, as you're likely to have multiple users of the app at some point.
I would take a hard look at the server code, adding further debugging information to the error messages in order to track down the problem.
I put request into for loop, it works. The first thought of rob about NSMutableRequest and NSURLSession seems right, I'm trying to catch the whole idea. Thanks for rob's answer. Anyway, this is code.
for (ImageInformation *temp in self.images.imageInfos) {
// compose request dynamically
NSURL *url = [NSURL URLWithString:#"http://121.199.35.173:8080/xihuan22dcloud/services/Shibietupianservice/serviceGetthetupian"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = #"POST";
NSString *data = [NSString stringWithFormat:#"uid=%#&yangzhengma=%#&tupianid=%#", uid, checkCode, temp.imageId];
request.HTTPBody = [data dataUsingEncoding:NSUTF8StringEncoding];
// data task
dispatch_async(self.serialQueue, ^{
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
// if client side is no errors, continue
if (!error) {
// if server side is no errors, continue
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
// in ram and put into images' imageRawData array
[self.images parseImageRawData:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] withImageId:temp.imageId];
// store data to disk
// [FCFileManager writeFileAtPath:path content:data];
// dispatch display image task to main
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.images.imageDrawDatasDic count] == [self.images.imageInfos count]) {
[self.tableView reloadData];
}
});
}
}
}] resume];
[NSThread sleepForTimeInterval:0.5];
});
}
}
We have more number of devices(200+) connected to network to communicate with server, often the connection fails and does not reconnect as mentioned in the question I am getting the mentioned errors.
The code which we use to send request is
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url=[NSURL URLWithString:[NSString stringWithFormat:#"http://ipaddress"];
NSMutableURLRequest *menuRequest=[[NSMutableURLRequest alloc]initWithURL:url];
NSURLResponse *response;
NSError *error=nil;
NSData *data = [NSURLConnection sendSynchronousRequest:menuRequest returningResponse:&response error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
if(!error)
{
NSLog(#"Request Success");
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData: data];
[xmlParser setDelegate:self];
[xmlParser parse];
}
else
{
NSLog(#"Failed with error-----%#",error);
}
});
});
This request is sent every minute.
It happens only in client environment we need to recover from this connection failure and reconnect(the next request does not get success even if the server is up).This happens in both iOS 7 & 8.I went through some similar post but I am not able to get the exact reason and also the solution for these problems.Help me out guys.
"I went through some similar post but i am not able to get the exact reason and alos the solution for these problems.Help me out guys."
The errors you are getting are the reasons. Specifically, codes -1001, -1005, and -1004 in the NSURLErrorDomain are time outs, network connection lost, and cannot connect to host. You can look these error codes up in the NSURLErrors.h header. These are all problems with your network connectivity.
The code you have posted has no logic to reconnect, which is why it does not attempt to do so.
Like #quellish said, these errors tell you what happened. I googled a thousand times to investigate codes like -1004. But it is simpler to find them locally...
XCode / Window / Developer Documentation / URL Loading System Error Codes
I'm trying to cache a webpage that I can then later show using a UIWebView.
I have the relevant NSURLSessionDataTask inside a for loop (trying to cache 6 webpages) inside the completion block of another NSURLSessionDataTask. When I run, I keep getting this error:
Ayy there was error downloading, data:<>
response:(null)
error:Error Domain=NSURLErrorDomain Code=-1002 "The operation couldn’t be completed. (NSURLErrorDomain error -1002.)" UserInfo=0xdd89d30 {NSUnderlyingError=0xdd89ba0 "The operation couldn’t be completed. (kCFErrorDomainCFNetwork error -1002.)"}
Here's a snippet of what I'm calling
for (MAClass *class in [myDictResult objectForKey:#"classes"]) {
NSString *PRURL = [[[class assignments] objectAtIndex:[[class assignments] count]-1] assignmentName];
NSLog(#"PRURL is %#", PRURL);
NSURLSessionDataTask *progressReportTask = [defaultSession dataTaskWithURL:[NSURL URLWithString:PRURL] completionHandler:^(NSData *progressReportData, NSURLResponse *progressReportResponse, NSError *progressReportError) {
if ([progressReportData length] > 0 && progressReportError == nil) {
NSLog(#"got dat data");
} else NSLog(#"Error with getting data data:%#\nresponse:%#\nerror:%#", progressReportData, progressReportResponse, progressReportError);
}];
[progressReportTask resume];
NSLog(#"After request");
}
I've made sure that the URL is valid, seeing that was the cause for other people with getting the same error (my urls are like https://mistar.oakland.k12.mi.us/novi/StudentPortal/Home/PrintProgressReport/20152193^HS4, which are valid when I put them into a browser)
What am I doing wrong?
-1002 is NSURLErrorUnsupportedURL/kCFURLErrorUnsupportedURL. In the future, you can either search the Xcode documentation for NSURLErrorDomain or use quick open (shift+command+O) to browser the headers for the definition of NSURLErrorDomain. Either technique would have lead you to discover that -1002 in NSURLErrorDomain is NSURLErrorUnsupportedURL.
The reason for this error is that your URL contains some characters that have to be percent escaped. And web browsers will frequently do the necessary percent-escaping for you, which is why it works there.
You can use stringByAddingPercentEscapesUsingEncoding to convert the URL to an acceptable format:
NSString *urlString = #"https://mistar.oakland.k12.mi.us/novi/StudentPortal/Home/PrintProgressReport/20152193^HS4";
NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionTask *task = [defaultSession dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
...
}];
By the way, when reconciling web browser results against the app, Charles is very useful. Run the request from browser and again from the app and compare the results in Charles. If you had compared these, you would have seen that you needed to percent escape the URL.
By the way, you can also refer to section 2 of RFC 3986 for a technical description of what characters in URLs must be percent escaped.
If your urlString contains a query string, also consider using NSURLQueryItem to build the queryString.
It will create the URL in its acceptable format.
Here is an example of how to put it into use: Building URLs with NSURLQueryItems and NSURLComponents.