Restkit: migrating to 0.20 - macos

I am trying to migrate to RestKit 0.20-pre2.
Currently I managed to migrate my mapping (at least the compiler does not complain anymore), but I have problems in creating requests (previously I used the RKObjectLoader which does not exist anymore.
My previous code is the following:
- (RKObjectLoader*)objectLoaderWithResourcePath: (NSString*)resourcePath
method: (RKRequestMethod)httpMethod
parameters: (NSDictionary*)parameters
mappableClass: (Class)objectClass
{
RKObjectMapping *mapping = [self.objectManager.mappingProvider objectMappingForClass:objectClass];
NSString *path = resourcePath;
if (httpMethod == RKRequestMethodGET) {
path = [resourcePath stringByAppendingQueryParameters:parameters];
}
RKObjectLoader *request = [self.objectManager loaderWithResourcePath:path];
request.method = httpMethod;
request.delegate = self;
request.objectMapping = mapping;
if (httpMethod != RKRequestMethodGET) {
request.params = parameters;
}
return request;
}
I used the above method to create a generic request, and then send it either synchronously or asynchronously.
Now... I saw the new method getObjectsAtPath: parameters: success: failure:, but.. I need the same for the POST (and I don't have any object to post... it is simply the server which accept a POST request for the login..)
Any help?
Thank you

I had the same problem as you and i received a great answer here:
Trying to make a POST request with RestKit and map the response to Core Data
Basically,
This is what you need:
NSDictionary *dictionary = #{ #"firstParam": #(12345), #"secondParam": #"whatever"};
NSMutableURLRequest *request = [objectManager requestWithObject:nil method:RKRequestMethodPOST path:#"/whatever" parameters:parameters];
RKObjectRequestOperation *operation = [objectManager objectRequestOperationWithRequest:request ^(RKObjectRequestOperation *operation, RKMappingResult *result) {
NSLog(#"Loading mapping result: %#", result);
} failure:nil];
There is an example here in README section Managed Object Request that will help you:
https://github.com/RestKit/RestKit

You can use AFNetworking directly using the RK HTTPClient subclass, something like this:
[[RKObjectManager sharedManager].HTTPClient postPath:#"/auth" parameters:params success:^(AFHTTPRequestOperation *operation, id JSON)
{
// Success
} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
// Error
}];
Since RestKit v0.20.x, RK now use AFNetworking under the hood instead of RKClient, so you can refer directly to the AFNetworking docs:
http://afnetworking.github.com/AFNetworking/Classes/AFHTTPClient.html#//api/name/postPath:parameters:success:failure:
Edit
In my project, for the auth, I simply created an NSObject named User, with a singleton, and managed the mapping myself. I (personally) didn't need to have my auth user in my core data stack. If you need to use the RK Core data mapping capabilities, take a look at RKObjectManager with the postObject:path:parameters:success:failure: method.

Related

Parameters are always null when AFJSONRequestSerializer is used for a POST in AFNetworking

I'm writing a REST API by using SlimFramework in server side and AFNetworking in client side.
I'd like to add a value in Header for Authorization so that I'm using AFJSONRequestSerializer before the POST. Here is my code:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager.requestSerializer setValue:self.apikey forHTTPHeaderField:#"Authorization"];
[manager POST:url parameters:#{REST_PARAM_USERID: userId}
success:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
But the failure callback is always called. I found that's because the parameters I passed to server are always null although they're not null when I debugged in my Xcode. When I comments out the requestSerializer then my server works well.I don't know what's the reason. Can anybody help? Thanks
When you use AFJSONRequestSerializer, your parameters will always be serialized as JSON in the body of the HTTP request. If your server is not expecting JSON, then you should either reconfigure your server, or not use AFJSONRequestSerializer.
If, for some reason, you want to send some parameters through normal URL encoding, and others through JSON, you'll need to manually append them to your URL like so:
NSString *urlWithParams = [NSString stringWithFormat:#"%#?%#=%#", url, REST_PARAM_USERID, userId"];
[manager POST:urlWithParams parameters:#{#"some other" : #"params"}
success:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];

How to add CSRF token into http header on AFHTTPRequestOperationManager?

I created a test application (using scaffold) at heroku and I built an iOS client (using AFNetworking 2) to this heroku application. I was trying to delete records from heroku using iOS app and It didn't work. I received 422 status error from server.
Looking at heroku logs I figure out that server is claiming for CSRF token. So I tried to do that with this code on my iOS client:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer new];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:#"application/json", nil];
[manager DELETE:contact.url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Response: %#", [operation description]) ;
if (block) {
block(error);
}
NSLog(#"Error: %#", error);
}];
It didn't work.
How can I add CSRF token into http header on AFHTTPRequestOperationManager ?
Two things here:
If your server is complaining about a lack of a CSRF token, then forging one isn't going to be the correct solution. See this Stack Overflow answer for more information. While you're just starting to get everything up-and-running, you can temporarily disable this by commenting out the protect_from_forgery call in your API controller.
AFHTTPRequestOperationManager is initialized with an AFJSONResponseSerializer already, so you don't need to set that yourself. You can add a default X-XSRF-TOKEN (or whatever header by doing [manager.requestSerializer setValue:#"..." forHTTPHeaderField:#"..."];
With AFNetworking2 you customize http headers in request serializer. So you need to subclass the one you currently work with and add this logic there.
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(NSDictionary *)parameters
error:(NSError *__autoreleasing *)error {
NSMutableDictionary *modifiedParams = [parameters mutableCopy];
modifiedParams[#"your-header-name"] = #"you-header-value";
NSMutableURLRequest *res = [super requestBySerializingRequest:request
withParameters:modifiedParams
error:error];
return res;
}

Storing UIManagedDocuments when uibiquity container (iCloud) is not available

I've managed to understand how to incorporate UIManagedDocument into a simple test application and it works as expected! However, now I'm adding support to this basic application so it will work if the user does not want to use iCloud.
So when the URLForUbiquityContainerIdentifier: method returns 'nil', I return the URL of the local documents directory using the suggested method
NSString *documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
return [NSURL fileURLWithPath:documentsDirectoryPath];
However, when I try saving a UIManagedDocument to the local URL (such as: file://localhost/var/mobile/Applications/some-long-identifier/Documents/d.dox) I get the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.'
Using this save method:
if (![[NSFileManager defaultManager] fileExistsAtPath:self.managedDocument.fileURL.path]) {
[self.documentDatabase saveToURL:self.managedDocument.fileURL
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
//
// Add default database stuff here.
//
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.documentDatabase.managedObjectContext performBlock:^{
[Note newNoteInContext:self.managedDocument.managedObjectContext];
}];
});
} else {
NSLog(#"Error saving %#", self.managedDocument.fileURL.lastPathComponent);
}
}];
}
It turns out my persistent store options contained the keys used for the ubiquitous store. These shouldn't be in the documents persistent store options.

How to tell RestKit which mapping to use when using sendObject

I'm new to RestKit so this question may be dumb: When using [objectManager sendObject:...], how do I tell RestKit which mapping it should use for the result? More specific: I am sending GET and POST data to a server which in return responds with a JSON encoded message:
{"purchaseresult":{"status":"ok","errormsg":""}}
The Objective-C code I use looks like this:
RKObjectMapping *purchaseResultMapping = [RKObjectMapping mappingForClass:[PurchaseResult class]];
[purchaseResultMapping mapKeyPathsToAttributes:#"status", #"status", #"errormsg", #"errorMessage",nil];
[objectManager.mappingProvider setMapping:purchaseResultMapping forKeyPath:#"purchaseresult"];
[[RKObjectManager sharedManager].mappingProvider setErrorMapping:purchaseResultMapping];
[objectManager sendObject:queryParams toResourcePath:#"/purchase/post" usingBlock:^(RKObjectLoader* loader) {
loader.method = RKRequestMethodPOST;
loader.resourcePath = #"/purchase/post";
loader.params = queryParams;
loader.objectMapping = purchaseResultMapping;
}];
This returns an RestKit error:
restkit.network:RKObjectLoader.m:216 Encountered errors during mapping: Expected an object mapping for class of type '__NSDictionaryI', provider returned one for 'PurchaseResult'
Any ideas what I am doing wrong here?
Thanks
Christian
The object that you're sending is a NSDictionary so RestKit is looking for an object mapping for an NSDictionary but you are providing a PurchaseResult mapping. If you send an instance of PurchaseResult or provide your mapping for the NSDictionary class then this error should be avoided.
An alternative to this would be as follows:
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/purchase/post" usingBlock:^(RKObjectLoader *loader) {
loader.delegate = **Your loader delegate here**;
loader.method = RKRequestMethodPOST;
loader.params = queryParams;
}];
Just make sure you've defined your mapping for the objects coming back in and you should be good.

Using NSOutputStream to POST to url

So all I want to do is send a POST request to a url. Now I tried using NSURLRequest/NSURLConnection, but was having problems with that and decided to move to a lower level, also because I want to send large files and thought dealing directly with streams might be better. But the output stream delegate never seems to be called, and I can't seem to find examples using NSOutputStream's initWithURL.
NSURL *url = [NSURL URLWithString:NSString stringWithFormat: #"http://url"];
self.outputStream = [[NSOutputStream alloc] initWithURL:url append:NO];
[outputStream retain];
[outputStream setDelegate:self];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
It seems that the outputStream is null after the init, which I can't understand because my url is a valid url--I can ping it from the terminal and send data from other sources. Am I doing something wrong, or can anyone tell me how to write a POST request to a URL using streams? Thanks.
You cannot use NSOutputStream to send a POST request to a HTTP server.
The good method is to create a NSMutableURLRequest and provide that request with a HTTPBodyStream then create a NSURLConnection to send the request.
The HTTPBodyStream is an NSInputStream the request will read the body from. You may initialize it with a NSData object or with the contents of a file.
If you want to provide a custom content (for example, you want to upload a file as part of a multipart/form-data request), you may need to subclass NSInputStream. In such case, I suggest you to have a look at How to implement CoreFoundation toll-free bridged NSInputStream subclass, which explains how to address an issue that occurs when using custom input streams with NSURLConnection. Or you may use ASIHTTPRequest which provides multipart HTTP requests out of the box.
Yes, you can send the HTTP GET/POST request using NSOutputStream.
1.make & open your stream.
2.when the stream is ready to write, the NSStreamEventHasSpaceAvailable event will be send in method:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
https://developer.apple.com/documentation/foundation/nsstreamdelegate/1410079-stream?language=objc
3.make a CFHTTPMessageRef & and write the data.
code like this:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
switch (eventCode) {
case NSStreamEventOpenCompleted:{
//
}
break;
case NSStreamEventHasSpaceAvailable:{
[self sendHTTPMessage];
}
break;
default:{
//
}
break;
}
- (void)sendHTTPMessage{
//create a http GET message
CFStringRef requestMethod = (CFStringRef)CFAutorelease(CFSTR("GET"));
CFHTTPMessageRef httpRequest = (CFHTTPMessageRef)CFAutorelease(CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, (__bridge CFURLRef)yourURL,kCFHTTPVersion1_1));
//set HTTP header
CFHTTPMessageSetHeaderFieldValue(httpRequest, (__bridge CFStringRef)#"Host", (__bridge CFStringRef)#"yourhost.com");
CFHTTPMessageSetHeaderFieldValue(httpRequest, (__bridge CFStringRef)#"Connection", (__bridge CFStringRef)#"close");
//set HTTP Body
...
//let's send it
CFDataRef serializedRequest = CFHTTPMessageCopySerializedMessage(httpRequest);
NSData *requestData = (__bridge_transfer NSData *)serializedRequest;
[self.outStream write:requestData.bytes maxLength:requestData.length];
[self.outStream close];
}
Remember, the key point is converting CFHTTPMessageRef to bytes to write.

Resources