I just started ios development and I'm trying to exchange data with my api. When I'm doing POST requests everything is going fine but when I'm trying to do a GET request I get the following error:
Error Domain=NSURLErrorDomain Code=-1017 "The operation couldn’t be
completed. (NSURLErrorDomain error -1017.)" UserInfo=0x145a2c00
{NSErrorFailingURLStringKey=http://myAPI.com/,
_kCFStreamErrorCodeKey=-1, NSErrorFailingURLKey=http://myAPI.com,
_kCFStreamErrorDomainKey=4, NSUnderlyingError=0x145b21d0 "The operation couldn’t be completed. (kCFErrorDomainCFNetwork error
-1017.)"}
Could someone explain what's going wrong and how I can fix it?
My request:
-(void)hitApiWithURL:(NSString*)url HTTPMethod:(NSString*)HTTPMethod params:(NSDictionary*)params successBlock:(successTypeBlock)success failureBlock:(errorTypeBlock)failure{
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
[sessionConfig setHTTPAdditionalHeaders:#{#"Content-type": #"application/json"}];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
[request setHTTPMethod:HTTPMethod];
// The body
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
[request setHTTPBody:jsonData];
NSURLSessionDataTask *dataTaks = [session dataTaskWithRequest:request];
[dataTaks resume];
NSLog(#"dataTask started");
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
if (error) {
//Gives my error
}
else {
// do something:
}
}
If you forget to mention HTTPMethod, the error can also take place, I faced the Problem "NSURLErrorDomain Code=-1017", somehow i forget to add line "request.HTTPMethod = "POST".
After adding the line, my code worked perfectly.
You have something wrong with your JSON parameters:
kCFURLErrorCannotParseResponse = -1017
This error occurs when you perform a GET request with the body set (setHTTPBody)
It isn't direct solution to this case but might help someone with -1017 error.
I had this issue after upgrading Alamofire from 4.8.0 to 5.2.2. Problem was with HTTPHeader.
var authorizationHeader: HTTPHeader {
guard let session = user?.session else {
return HTTPHeader(name: "", value: "")
}
return HTTPHeader(name: "Authorization", value: "Bearer \(session)")
}
Apparently sending HTTPHeader with empty key-value i-e HTTPHeader(name: "", value: "") triggers this error. I adjusted my code to instead return nil and it solved the issue.
For any other people who got error code -1017 -- I fixed it by manually setting my http headers in my http request. I think the server was having problems parsing the HTTP headers because instead of the string "Authorization" : "qii2nl32j2l3jel" my headers didn't have the quotes like this: Authorization : "1i2j12j". Good luck.
Something like this:
NSMutableDictionary* newRequestHTTPHeader = [[NSMutableDictionary alloc] init];
[newRequestHTTPHeader setValue:authValue forKey:#"\"Authorization\""];
[newRequestHTTPHeader setValue:contentLengthVal forKey:#"\"Content-Length\""];
[newRequestHTTPHeader setValue:contentMD5Val forKey:#"\"Content-MD5\""];
[newRequestHTTPHeader setValue:contentTypeVal forKey:#"\"Content-Type\""];
[newRequestHTTPHeader setValue:dateVal forKey:#"\"Date\""];
[newRequestHTTPHeader setValue:hostVal forKey:#"\"Host\""];
[newRequestHTTPHeader setValue:publicValue forKey:#"\"public-read-write\""];
//the proper request is built with the new http headers.
NSMutableURLRequest* request2 = [[NSMutableURLRequest alloc] initWithURL:request.URL];
[request2 setAllHTTPHeaderFields:newRequestHTTPHeader];
[request2 setHTTPMethod:request.HTTPMethod];
PLease check this link
Alamofire.request(method, "(URLString)" , parameters: parameters, headers: headers).responseJSON { response in }
in post method you also add
parameters:parameters, encoding: .JSON
(if you added encoding line to GET POST then error will come "cannot parse response")
2nd please check the header as well.
["authentication_token" : "sdas3tgfghret6grfgher4y"]
then Solved this issue.
For me when send one empty header like ["":""]. Triggers this error 1017.
Solution, if you have declare:
let headers = ["":""]
the best one option is set to nil:
let headers = nil
Then you can:
sessionManager.request(
"www.example.com",
method: .post,
parameters: parameters,
encoding: encoding,
headers: headers
).responseJSON { response in....
Related
I have an application which implements remote notifications via firebase messaging api. In this app, I have implemented a notification service extension, which among others, implement UNNotificationActions.
In one of these actions, I've implemented an input field where you can write something, which then should be posted to firestore.
I've tried implementing this, but without success. So my question is how can I write to firestore from a rich notification running in the background - is this even possible?
My implementation looks like this:
let likeAction = UNNotificationAction(identifier: "likeAction", title: "Like", options: [])
let commentAction = UNTextInputNotificationAction(identifier: "commentAction", title: "Comment", options: [UNNotificationActionOptions.authenticationRequired], textInputButtonTitle: "Send", textInputPlaceholder: "Type your message")
let category = UNNotificationCategory(identifier: "posts", actions: [likeAction, commentAction], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
Then in AppDelegate, I implement the function to run whenever this is triggered like this:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
switch response.actionIdentifier {
case "commentAction":
guard let data = response.notification.request.content.userInfo["data"] as? [String: Any] else { return }
guard
let channelName = data["channelName"],
let postId = data["postId"]
else { return }
if let message = response as? UNTextInputNotificationResponse {
let documentPath = "\(channelName)/\(postId))"
let post = Post()
post.documentPath = documentPath
post.addComment(text: message.userText, postDocumentPath: documentPath)
}
I've debugged the code, and the method post.addComment() does actually get fired, and every field has a value. When I check the database, nothing gets inserted into it. The console prints out this, which I don't know if is related to the problem, I haven't been able to find anything online about these lines:
dnssd_clientstub deliver_request ERROR: write_all(21, 65 bytes) failed
nssd_clientstub read_all(26) DEFUNCT
When running the post method, no error from firebase comes up.
This was the initial information I could think of. I can provide more code or info if need be.
Update
I've discovered if I press the button while on lock screen, but with the iPhone in an unlocked state, nothing happens - as soon as I swipe up, and the app shows, the request gets send. This does indeed seem to be a background issue.
Kind regards Chris
So I finally found a solution / workaround.
It turns out that firebase will not post in the background, instead it stores the data locally, until the app comes into foreground, then it will post it.
The solution was to add another firebase function that listens for HTTP requests. In this function, I added a method to post to the database with the data from the action.
I then do a regular but modified http post request with Alamofire from the action to the url like this:
let headers: HTTPHeaders = [
"Authorization": "Bearer \(token)",
"Content-Type": "application/json"
]
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 15.0
configuration.timeoutIntervalForResource = 15.0
configuration.waitsForConnectivity = true
self.alamofireManager = Alamofire.SessionManager(configuration: configuration)
guard let manager = self.alamofireManager else {
return
}
manager.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseString(completionHandler: { (response) in
let _ = manager
print(response)
closure(true)
})
The important part of this, for me, was to set configuration.waitsForConnectivity = true otherwise, it would come back saying no connection to the internet.
This enables the function to comment on a notification from the lock screen.
Hope this information helps others looking for the same.
For anyone using a standard NSURLSession to complete the firestore HTTP REST request (and not Alamofire like in #ChrisEenberg's excellent answer above)
[NB: A previous version of this answer used background tasks, which are not necessary. This edit uses an ordinary NSURLSessionDataTask upload task and appears to work when app is backgrounded or closed, as expected.]
OBJECTIVE-C
Inside didReceiveNotificationResponse:
Prepare request
// Grab authenticated firestore user
FIRUser *db_user = [FIRAuth auth].currentUser;
// Set up the response
NSDictionary *reqFields;
// Fields for firestore object (REST API)
NSDictionary *my_uid_val = [[NSDictionary alloc] initWithObjectsAndKeys: (db_user.uid ?: [NSNull null]), (db_user.uid ? #"stringValue" : #"nullValue"), nil];
NSDictionary *action_val = [[NSDictionary alloc] initWithObjectsAndKeys: response.actionIdentifier, (response.actionIdentifier ? #"stringValue" : #"nullValue"), nil];
// Create object
reqFields = #{
#"my_uid": my_uid_val,
#"action": action_val
};
// Place fields into expected reqBody format (i.e. under 'fields' property)
NSDictionary *reqBody = [[NSDictionary alloc] initWithObjectsAndKeys:
reqFields, #"fields",
nil];
// Confirm...?
NSLog(#"%#", reqBody);
Compose the request
// Grab current user's token (for authenticated firestore REST API call)
[db_user getIDTokenWithCompletion:^(NSString * _Nullable token, NSError * _Nullable error) {
if (!error && token) {
NSLog(#"Successfully obtained getIDTokenWithCompletion: %#", token);
// Compose stringified response
// + (per https://stackoverflow.com/a/44923210/1183749 )
NSError *error;
NSData *postData = [NSJSONSerialization dataWithJSONObject:reqBody options:kNilOptions error:&error];
if(!postData){
NSLog(#"Error creating JSON: %#", [error localizedDescription]);
}else{
NSLog(#"Successfully created JSON.");
}
// Create the request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://firestore.googleapis.com/v1beta1/projects/%#/databases/%#/documents/%#", #"project_id", #"(default)", #"collection_id"]]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setValue:[NSString stringWithFormat:#"Bearer %#", token] forHTTPHeaderField:#"Authorization"]; // 'token' is returned in [[FIRAuth auth].currentUser getIDTokenWithCompletion]. (All of this code resides inside the getIDTokenWithCompletion block so we can pass it along with the request and let firebase security rules take care of authenticating the request.)
[request setHTTPBody:postData];
// Set up the session configuration
NSURLSessionConfiguration *sessionConfig;
sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
// Set up the session
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
Start the upload task
// Start the upload task
NSURLSessionDataTask *uploadTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *uploadTaskResp, NSError *error) {
NSLog(#"dataTask Request reply: %#", uploadTaskResp);
if(error){
NSLog(#"dataTask Request error: %#", error);
}
// Call completion handler
completionHandler();
}
}
}
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 have a URL that I'm trying to get XML from.
Now in my iOS app I have this, to get the data.
- (void)viewDidLoad {
[super viewDidLoad];
[self loadDataUsingNSURLConnection];
}
- (void) loadDataUsingNSURLConnection {
NSString *url = #"http://64.182.231.116/~spencerf/university_of_albany/u_albany_alumni_menu_test.xml";
[self getMenuItems:url];
}
And then finally this,
- (void)getMenuItems:(NSString*)url{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSLog(#"response == %#", response);
NSLog(#"data: %#", data);
/*
self.mealdata=[[MealData alloc]init:data];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[self.loadingView removeFromSuperview];
});
*/
}];
}
Now sometimes when I run my app it works great and data is returned, but then sometimes, I would say about 25% of the time, with me changing nothing in-between runs. It returns no data, and the NSLog returns
2015-03-10 18:28:05.472 APP[6289:97905] response == <NSHTTPURLResponse:
0x7fee7628e000> { URL: http://64.182.231.116/~spencerf/university_of_albany/u_albany_alumni_menu_test.xml } { status code: 200, headers {
"Accept-Ranges" = bytes;
Connection = "Keep-Alive";
"Content-Length" = 0;
"Content-Type" = "application/xml";
Date = "Tue, 10 Mar 2015 22:28:04 GMT";
Etag = "W/\"218ceff-0-510f6aa0317dd\"";
"Keep-Alive" = "timeout=5, max=90";
"Last-Modified" = "Tue, 10 Mar 2015 22:28:03 GMT";
Server = Apache;
} }
2015-03-10 18:28:05.472 App[6289:97905] data: <>
Not sure why this is happening, and I can't tell the difference between when it works and when it doesn't what is changing? So Im not sure how to fix this?
What I want is it to get the data every time?
Thanks for the help in adavence.
Try to increase timeout interval for your request:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
[request setTimeoutInterval:60.0];//seconds
Your code is fine. It is the server that is giving you trouble.
I reloaded the page that you mentioned 20 times. It successfully loaded 13 times. 7 times it returned no error but also an empty response body.
What you can do is simply check for this condition in your app and run the request again to try again.
Or talk to the server owner to find out if they can improve the reliability of this service.
Running your code on device, when there is no data received, error message is either null or Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost.".
As to the latter error, you can see AFNetworking/issues/2314, below is a comment extracted from the post.
when iOS client received HTTP response with a Keep-Alive header, it
keeps this connection to re-use later (as it should), but it keeps it
for more than the timeout parameter of the Keep-Alive header and then
when a second request comes it tries to re-use a connection that has
been dropped by the server.
You could also refer to the solution here, if you can't change the server, I'd suggest you retry the request when error code is -1005 or received data length is 0.
Perhaps Long-Polling would help you. The web server intermittently returns data when I tried it.
Long-Polling will allow you to continue sending requests until you get the data you specify back.
This code shows you an example of how implement long-polling
- (void) longPoll {
//create an autorelease pool for the thread
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
//compose the request
NSError* error = nil;
NSURLResponse* response = nil;
NSURL* requestUrl = [NSURL URLWithString:#"http://www.example.com/pollUrl"];
NSURLRequest* request = [NSURLRequest requestWithURL:requestUrl];
//send the request (will block until a response comes back)
NSData* responseData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response error:&error];
//pass the response on to the handler (can also check for errors here, if you want)
[self performSelectorOnMainThread:#selector(dataReceived:)
withObject:responseData waitUntilDone:YES];
//clear the pool
[pool drain];
//send the next poll request
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
-(void) startPoll {
//not covered in this example: stopping the poll or ensuring that only 1 poll is active at any given time
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
-(void) dataReceived: (NSData*) theData {
//process the response here
}
Your URL returns 404 Not Found, so you should fix server part
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];
In order to send HTTP request, I am using NSURLConnection like this:
NSURLConnection *connection = [[NSURLConnection alloc]
initWithRequest:request
delegate:self
startImmediately:YES];
At the end of connectionDidFinishLoading, I need to post different notifications, depending on the HTTP request that was just completed.
However inside connectionDidFinishLoading I don't have a clear logical identifier to the type of the request that was send:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// here i want to post various notifications, depending on the HTTP request that was completed
}
What is the best solution here? Thanks!
Connection did finish method passes the NSURLConnection object, which has the request and url:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"currentRequest: %#", connection.currentRequest);
NSLog(#"originalRequest: %#", connection.originalRequest);
// here do a if statement that compares url
if ([connection.currentRequest.URL.absoluteString isEqualToString:#"http://google.co.uk"]) {
NSLog(#"Equal to google");
// post notification
}
}
You can use framework like MKNetworkKit. In this framework you can write like this:
- (void) sendRequest: (NSString*) aRequestPath
httpMethod: (NSString*) aHttpMethod
paramsBlock: (SMFillParametersForRequestBlock) aParamsBlock
successBlock: (SMSaveRequestResultBlock) aSuccessBlock
errorBlock: (SMErrorRequestResultBlock) aErrorBlock
userInfo: (id) anUserInfo
{
MKNetworkEngine* network_engine= [[MKNetworkEngine alloc] initWithHostName: MuseumsHostName];
NSMutableDictionary* params = [[NSMutableDictionary alloc] init];
if (aParamsBlock)
{
aParamsBlock(params);
}
MKNetworkOperation* operation = [network_engine operationWithPath: aRequestPath
params: params
httpMethod: aHttpMethod
ssl: NO];
[operation onCompletion: ^(MKNetworkOperation* completedOperation){
// parse response of current request:
aSuccessBlock(completedOperation, anUserInfo, ...);
} onError: ^(NSError *error){
// error handler: call block
}];
[network_engine enqueueOperation: operation];
}
Believe me, this is the best solution