I'm trying to do a fairly basic HTTP PUT using RestKit. I don't want to put the entire object, since the API call was designed to accept a single query parameter and just update that field. I've tried two approaches so far, both unsuccessful.
URL to post to: https://myserver/api/users/{userId}
Query string parameter: verificationCode=
Example usage: PUT https://myserver/api/users/101?verificationCode=646133
Approach #1: Put the query parameter in a RKParams object and make the PUT call with those params.
NSString *putUrl = [NSString stringWithFormat:#"/api/users/%i", [APIUserInfo sharedAPIUserInfo].apiUserIdx];
NSLog(#"the PUT url is %#", putUrl);
// Send a PUT to a remote resource. The dictionary will be transparently
// converted into a URL encoded representation and sent along as the request body
NSDictionary* paramsDict = [NSDictionary dictionaryWithObject:[_verificationCode text] forKey:#"verificationCode"];
// Convert the NS Dictionary into Params
RKParams *params = [RKParams paramsWithDictionary:paramsDict];
[[RKClient sharedClient] put:putUrl params:params delegate:self];
Approach #2: Build the entire url and try a PUT with params set to nil.
NSString *putUrl = [NSString stringWithFormat:#"/api/users/%i?verificationCode=%#", [APIUserInfo sharedAPIUserInfo].apiUserIdx, [_verificationCode text]];
NSLog(#"the PUT url is %#", putUrl);
[[RKClient sharedClient] put:putUrl params:nil delegate:self];
Neither approach is working for me. The first fails saying "RestKit was asked to retransmit a new body stream for a request. Possible connection error or authentication challenge?" then runs for about 10 seconds and times out. The second approach fails saying HTTP Status 405 - Method Not Allowed.
Can anyone point out where I'm going wrong, or provide me with a simple PUT example using RestKit? Most of the examples I've found at there are putting the entire object which I don't want to do in this case.
UPDATE:
Approach #2 worked well once I got a few things sorted out on the server side. Final solution:
NSString *putUrl = [NSString stringWithFormat:#"/api/users/verify/%i?verificationCode=%#", [APIUserInfo sharedAPIUserInfo].apiUserIdx, [_verificationCode text]];
NSLog(#"the PUT url is %#", putUrl);
[[RKClient sharedClient] put:putUrl params:nil delegate:self];
the HTTP PUT method is disabled on your webserver. It is by default on all webserver for security reasons.
HTTP Status 405 - Method Not Allowed.
Related
I looked through all the relevant questions I could find (search: "NSURLErrorDomain -1000"), and couldn't find a solution. Maybe somebody eagle-eyed could help me.
I'm making calls from iOS 6 to the Instagram API (but the parts that work, did so already in iOS 5.1). I'm using Koolistov's NSURL+PathParameters.h as a helper, and my understanding is that it does indeed escape everything that is necessary in the URL.
Some preliminaries:
NSDictionary *parameters = nil;
NSURL *URL = [NSURL URLWithString:#"https://api.instagram.com/"];
parameters = [NSDictionary dictionaryWithObjectsAndKeys:
INSTAGRAM_CLIENT_ID, #"client_id", nil];
URL = [URL URLByReplacingPathWithPath:#"/v1/tags/awesome/media/recent"];
URL = [URL URLByAppendingParameters:parameters];
(INSTAGRAM_CLIENT_ID is a macro that contains my app's client ID.)
This URL works as expected.
However, the URL constructed as follows gives me NSURLErrorDomain code = -1000 consistently (on device and on both iPhone 5.1 and 6.0 simulators):
double latitude = 48.858844;
double longitude = 2.294351;
parameters = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithDouble:latitude], #"lat",
[NSNumber numberWithDouble:longitude], #"lng",
[NSNumber numberWithInt:2000], #"distance",
INSTAGRAM_CLIENT_ID, #"client_id",
nil];
URL = [URL URLByReplacingPathWithPath:#"/v1/media/search"];
URL = [URL URLByAppendingParameters:parameters];
I then create an NSURLRequest:
NSURLRequest *req = [NSURLRequest requestWithURL:URL];
If I print out the URL from the NSURLRequest, it seems quite OK:
https://api.instagram.com/v1/media/search?client_id=REDACTED&distance=2000&lat=48.858844&lng=2.294351
I'm using MyDownloader from Matt Neuburg's Programming iOS 5, Chapter 37. I create an instance of MyDownloaderand pass it the NSURLRequest:
MyDownloader *downloader = [[MyDownloader alloc] initWithRequest:req];
Accoding to RFC 3986, underscores or dots don't need to be escaped, I don't see anything else to be escaped, and NSURL+PathParameters.h should handle escaping anyway. So what is going on here? What am I missing?
To add to the weirdness, also this even simpler way of constructing the URL gives the same error:
parameters = [NSDictionary dictionaryWithObjectsAndKeys:
INSTAGRAM_CLIENT_ID, #"client_id", nil];
URL = [URL URLByReplacingPathWithPath:#"/v1/media/popular"];
It also looks OK:
https://api.instagram.com/v1/media/popular?client_id=REDACTED
All of these URLs work in the Instagram API console and directly in Safari. These API calls don't need OAuth authentication, just the client ID.
I would be greatful if somebody could point out what the problem is.
EDIT: encoded the lat and lng values with:
[[[NSNumber numberWithDouble:latitude] stringValue]
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
No change in URL or behavior. But when I replace the dots with literal %2E's, the path helper encodes the percent sign, resulting in %252E...
Solved. There was nothing wrong with the URLs shown above. The problem was with surrounding code. I'm answering my own question so that someone working with the Instagram API might benefit from the answer.
It turns out that the /v1/tags/tagname/media/recent endpoint of the API returns a pagination object in the JSON response, but the /v1/media/search and /v1/media/popular do not. From inside that object you can find the URL to the next batch of photos, in a value called next_url.
Because I hadn't read the documentation properly (and it was a little confusing in places), and because the different Instagram API endpoints work differently, my code expected to find the next_url value in all those cases. So the "bad URL" I was getting from NSURLConnection was referring to the second connection, and not the initial URL. Since I did not realize there is no pagination in the two other cases, I assumed that the problem must be in the initial URL.
My user enters a recipients address (Street address not email). I need to verify it with the USPS so I know that it is actually an address.
I am digging through their API right now and I think I understand it but I'm not exactly sure how to go about it with objective-c.
So pretty much it works like so:
I have to create an XML request that contains the recipient name, address, and zip code.
I have to post that to their server
They respond with an XML response
Here is an example of what one of their constructed XML request looks like:
http://SERVERNAME/ShippingAPITest.dll?API=Verify&XML=<AddressValidateRequest% 20USERID="xxxxxxx"><Address ID="0"><Address1></Address1>
<Address2>6406 Ivy Lane</Address2><City>Greenbelt</City><State>MD</State> <Zip5></Zip5><Zip4></Zip4></Address></AddressValidateRequest>
A bit garbled but broken down:
http://SERVERNAME/ShippingAPITest.dll?API=Verify&XML=
<AddressValidateRequest% 20USERID="xxxxxxx">
<Address ID="0">
<Address1></Address1>
<Address2>6406 Ivy Lane</Address2>
<City>Greenbelt</City>
<State>MD</State>
<Zip5></Zip5>
<Zip4></Zip4>
</Address>
</AddressValidateRequest>
My first idea seems obvious but there maybe a better way to go about it. Since the XML feed short, should I go about construction by simple doing something along the lines of:
NSString *request = [NSString stringWithFormat:#"......"]
Where it is filled in and formatted along the lines posted above.
The second question is how to go about correctly sending this to the server?
I simply create a NSURL request and with the URL as the constructed XML string?
Here what I have but I keep getting that the URL was constructed wrong:
- (void)verifyAddress:(Recipient*)_recipient {
NSURL *_url = [NSURL URLWithString:#"http://testing.shippingapis.com/ShippingAPITest.dll?API=Verify&XML=<AddressValidateRequest%20USERID=\"********\"><Address ID=\"0\"><Address1></Address1><Address2>6406 Ivy Lane</Address2><City>Greenbelt</City><State>MD</State><Zip5></Zip5><Zip4></Zip4></Address></AddressValidateRequest>"];
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:_url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
receivedData = [NSMutableData data];
NSString* newStr = [[NSString alloc] initWithData:receivedData
encoding:NSUTF8StringEncoding];
NSLog(#"the response '%#'", newStr);
} else {
// Inform the user that the connection failed.
NSLog(#"error");
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// This method is called when the server has determined that it
// has enough information to create the NSURLResponse.
// It can be called multiple times, for example in the case of a
// redirect, so each time we reset the data.
// receivedData is an instance variable declared elsewhere.
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
// inform the user
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString* newStr = [[NSString alloc] initWithData:receivedData
encoding:NSUTF8StringEncoding];
NSLog(#"the response '%#'", newStr);
// do something with the data
// receivedData is declared as a method instance elsewhere
NSLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
}
I get the following error:
Connection failed! Error - bad URL (null)
My only question now is, am I doing everything ok as far as NSURLConnection goes? I can play around with the URL, I just want to make sure my implementation is ok so Im not running around in circles. :P
You have % 20 in your URL. It should be %20 (no space).
There may be other problems, but that was one was easy to spot. If you are getting an error message, you need to edit your question and paste in the exact error message.
Also, you might consider using Apple's NSURLRequest and NSURLConnection classes, because more people are likely to be familiar with them so it may be easier for you to find help.
Cory, I work in the address validation industry (for SmartyStreets, where what you're trying to do is our specialty in fact) and have seen a lot of similar issues to yours.
We actually used to support an XML-endpoint for our address verification API (LiveAddress). Last year we deprecated it and deployed a new JSON format because the XML was clunky to use and had a lot of problems when it's actually just a simple task (for you, the developer).
So a few things to keep in mind... and while Rob's answer is programmatically comprehensive, these are important to consider also:
The USPS is the official source of addresses for the USA, but its core domain is not providing API service. Especially with recent financial troubles, I suspect that support and maintenance of the API will wane over time.
The License Agreement for the API you're using is quite restrictive. For example:
User agrees to use the USPS Web site, APIs and USPS data to facilitate USPS shipping transactions only. [27 Jan 2012]
Meaning, if you're shipping mail or packages via the USPS by using their API, it's fine, but for any other purpose it's not allowed and it violates the TOS.
I see you're developing for iOS. There's a great JSON library for that called TouchJSON that, in my opinion, is easier to use than XML formats in Objective-C.
While the USPS service does work, they CASS-certify private entities to provide their data at a better value (more specialty, experience, features, etc).
These and other maladies can be remedied by service from a third-party vendor. More details and reasons are documented here. Which provider you choose is up to you, but I'll be happy to personally answer any other address-validation-related questions.
using loadObjectAtResourcePath on GET method, doesn't include my parameters on the requests.
for example, if I send:
[RKObjectManager objectManagerWithBaseURL:#"http://something/ws"];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/res" delegate:self block:^(RKObjectLoader *loader) {
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
#"val", #"param1",
nil];
loader.params = [RKParams paramsWithDictionary:dict];
}];
the final url request doesn't include the "?param1=val" part - why?
Update Months Later
The real answer is that loader.params creates the HTTP BODY, hence it works for POST, PUT, DELETE etc but not for GET where the params are appended to the URL.
Hence, the answer below still works if you're facing the same issue for GET, but if you're sending out GET requests, it's mostly using methods that attach the params to the query string.
To summarize the differences between the two.
Sending params in the HTTP Body(i.e. POST, UPDATE, DELETE)
// Convert a NS Dictionary into Params
RKParams *params = [RKParams paramsWithDictionary:optionValues];
// I use sendObject to skip the router. Otherwise it's normally postObject
[[RKObjectManager sharedManager] sendObject:yourObject toResourcePath: yourResourcePath usingBlock:^(RKObjectLoader *loader) {
loader.method = RKRequestMethodPOST;
loader.delegate = delegate;
loader.params = params; // This sets params in the POST body and discards your yourObject mapping
} ];
Caveat Emptor (for above)
Setting params in the block destroys any mapping that you might have set in yourObject, kind of defeats the purpose of using object mapping. There's a fix here by Sebastian loader.params - Extra params if you really want to use this method to append extra parameters to your Post not in the object.
Sending in params as Query String (i.e. GET)
// Make a NS dictionary and use stringByAppendingQueryParameters
NSDictionary *shopParams = [NSDictionary dictionaryWithKeysAndObjects:
#"limit",#"20",
#"location",#"latitude,longitude",
nil];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/v1/shops.json" stringByAppendingQueryParameters:shopParams] delegate:objectDelegate];
The rest of the answer is just for reference, I'm a hoarder.
Old Answer
I'm using RestKit for my project and facing the same issue.
I think RKParams is mainly used to do POST requests. I cannot fully decipher your code because 1) I don't know loader's declaration? 2) RKParams is not to be used with Object Manager?
I did this.
Loader Method in App Delegate
NSDictionary *shopParams = [NSDictionary dictionaryWithKeysAndObjects:#"limit",#"30", nil];
[[RKClient sharedClient] get:#"/api/v1/shops.json" queryParams:shopParams delegate:self];
Delegate
- (void)requestDidStartLoad:(RKRequest *)request {
NSLog(#"RK Request description: %#",[request description]);
}
Output:
RK Request description: <RKRequest: 0x7993db0> and rails log say {"limit"=>"30"}.
From the autocomplete in Xcode, you can see the get request didn't even use RKParams. Just a NSDict. The POST requests uses it.
My goal is to attach a query string, i.e. ?location=singapore&etcetc to my API methods in Rails. For this, RK comes with a NSString addon called appendQueryParams RK docs link that you can use to append query params.
If your goal is POST images etc, you can follow the above line of thought of using RKClient.
Update:
If you just want to append parameters to Object Manager
NSDictionary *shopParams = [NSDictionary dictionaryWithKeysAndObjects:
#"limit",#"20",
#"location",#"latitude,longitude",
nil];
This is outdated and marked for deprecation.
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/v1/shops.json" appendQueryParams:shopParams] delegate:self];
Use this instead:
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/v1/shops.json stringByAppendingQueryParameters:shopParams] delegate:yourLoaderDelegate];
Rails Log: {"location"=>"latitude,longitude", "limit"=>"20"}
Hope in my answer I didn't make any wrong statements.
Refer to this question RestKit GET query parameters.
Hi I am using restkit for first time, and there are several questions that come to my mind. First when sending a post request using restkit what format is the request Json or XML and how can I specify it? I am sending a post request to the server to authenticate a user and should receive a conformation if details correct in XML format.
NSArray *objects = [NSArray arrayWithObjects: email, password, nil];
NSArray *keys = [NSArray arrayWithObjects:#"username", #"password", nil];
NSDictionary *params = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
[[RKClient sharedClient] post:#"/login" params:params delegate:self];
This is the code I am using, the xml accepted by the web services should look like
<login>
<username>user#example.com</username>
<password>Password</password>
</login>
It is sending the request,but I am not getting the right response. Is there a way to view what is the format of the request I am sending to the server ?
Sounds like you are using the wrong call. The post call you are using assumes that the rest service wants params like login=username&password=skdjgh, i.e. NOT in XML, but in 'normal REST format'. You need to either find a call to post a block of text using RestKit, or use another call. In other words you need to create the XML yourself (or use some library) then send that via a post.
Give this a try. I think it is supposed to do what you want. I never got it to work but I had other things wrong with my code.
RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:#"http://mysite.com"];
objectManager.serializationMIMEType = RKMIMETypeXML;
Note: this may be what you do for sending XML back to you, not sending XML to the server. Don't know off the top of my head.
You can use RKRequestSerialization class to do the xml serialization for you.
Here is a code snippet from one of my projects:
#import <RestKit/RKRequestSerialization.h>
...
[RKObjectManager sharedManager].acceptMIMEType = RKMIMETypeTextXML;
NSString *loginData = #"<Login><UserId>test</UserId><Password>test</Password></Login>";
[[RKClient sharedClient] setValue:#"XYZ01" forHTTPHeaderField:#"ServiceId"];
[[RKClient sharedClient] post:#"/login"
params:[RKRequestSerialization serializationWithData:[loginDetails dataUsingEncoding:NSUTF8StringEncoding] MIMEType:RKMIMETypeTextXML]
delegate:self];
And, here is the dump using RKLogConfigureByName("RestKit/Network", RKLogLevelTrace)
2012-08-12 11:28:41.476 MyApp[730:707] T restkit.network:RKRequest.m:402 Prepared POST URLRequest '<NSMutableURLRequest https://someserver.com/MyApp/rest/service/request>'. HTTP Headers: {
Accept = "text/xml";
"Content-Length" = 75;
"Content-Type" = "text/xml";
ServiceId = XYZ01;
}. HTTP Body: <Login><UserId>test</UserId><Password>test</Password></Login>
I did objectManager.serializationMIMEType = RKMIMETypeXML; but the Content-Length of my request is 0, and the HTTP Body is empty. If I use RKMIMETypeJSON the request looks fine, but the xml serialization doesn't work. Any ideas why this happens? What else do I need to do to post xml with valid serialization?
I also found this in doc: "RestKit currently supports serialization to RKMIMETypeFormURLEncoded and RKMIMETypeJSON". So, how can I use RKObjectManager to post xml?
I solved this using ASIHTTPRequest and XMLWriter. Unfortunate, but RestKit just doesn't seem to support XML posts out the box.
I do use RK for GETting stuff though.
In my application, i have a button wich calls an ASIHTTPRequest. The request goes fine and i receive an response string. But when the request finishes, it always goes to the method: requestFinished. And i also got a requestFailed method. But even when i give a wrong link, the request finsihes and never fails.. Why is this? This is my code:
-(void)fetchForm:(id)sender {
NSURL *URL=[NSURL URLWithString:#"http://www.mydomain.nl/testGet.php"];
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:URL] autorelease];
[request setDelegate:self];
[request setDidFailSelector:#selector(requestFailed:)];
[request startAsynchronous];
}
-(void)requestFinished:(ASIHTTPRequest *)request {
NSLog(#"Request Success!");
}
-(void)requestFailed:(ASIHTTPRequest *)request {
NSLog(#"Request Failed!");
}
EDIT:
I read a little documentation on the ASIHTTPRequest website. And i came to the conclusion that i need to see for myself if there is an error code. i do this with:
int statusCode = [request responseStatusCode];
if (statusCode == 404) {
NSLog(#"Statuscode 404 has occured!");
}
There are several conditions that might affect error reporting with HTTP requests. From ASIHTTP's viewpoint, if the request object can be successfully built, sent, and a some kind of response is received, then everything is ok.
In my case, for example, my ISP has a proxy that will return an error page with many not existing URLs and sometimes even with ill-formed URLs. In such cases, ASIHTTP will not fail. I don't know if this is also your case, but it was for me.
If you look at the file ASIHTTPRequest.m and search for failWithError, you will see all the cases where ASIHTTP will trigger the mechanism that leads to the didFailSelector to be called. You might even set a breakpoint in the failWithError method to see if it is called.
EDIT:
In a sense ASIHTTPRequest mechanism is very basic and covers failures at the network level. If you receive a response then it is an application level failure and you have to deal with it.
First thing is checking the HTTP status code:
int statusCode = [request responseStatusCode];
NSString *statusMessage = [request responseStatusMessage];
This will allow you to identify 404, 500, and so on.
If this does not work and the server does not send an error code, then the only way to go about it is parsing the response you receive and, if it does not contain the data you were waiting for, fail.
try this one -
[request setDidFailSelector:#selector(requestFailed:)];
try this:-
[request setDidFinishSelector:#selector(requestFinished:)];
[request setDidFailSelector:#selector(requestFailed:)];
Write these two lines and then try.