NSURLSession request HTTPBody becomes nil in Custom NSURLProtocol - nsurlsession

In my application, I am performing POST using NSURLSession.
Steps followed:
Setting Header
Setting HTTPBody
Making POST request using NSURLSession.
The code is:
NSDictionary *parameters = #{ #"searchTerm": #"shampoo", #"sort": #"Most Reviewed" };
NSError *error = nil;
NSData *postData = [NSJSONSerialization dataWithJSONObject:parameters options:NSJSONWritingPrettyPrinted error:&error];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"SomeURL"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
[request setHTTPMethod:#"POST"];
[request setAllHTTPHeaderFields:headers];
request.HTTPBody = postData;
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"%#", error);
} else {
NSLog(#"Pass");
}
}];
[dataTask resume];
Now in custom NSURLProtocol class:
(BOOL)canInitWithRequest:(NSURLRequest *)request {
if ([request.HTTPMethod isEqualToString:#"POST"]) {
//here request.HTTPMethod is coming nil
//Whereas my requirement is to get request.HTTPMethod which got request parameter.
return YES;
}
return NO;
}
Thanks in advance.

IIRC, body data objects get transparently converted into streaming-style bodies by the URL loading system before they reach you. If you need to read the data:
Open the HTTPBodyStream object
Read the body data from it
There is one caveat: the stream may not be rewindable, so don't pass that request object on to any other code that would need to access the body afterwards. Unfortunately, there is no mechanism for requesting a new body stream, either (see the README file from the CustomHTTPProtocol sample code project on Apple's website for other limitations).

Related

Set NSData dataWithContentsOfURL timeout

So i have this method in my app that returns BOOL if and update is available for my apps content
- (BOOL)isUpdateAvailable{
NSData *dataResponse=[NSData dataWithContentsOfURL:[NSURL URLWithString:#"url that returns json object"] ];
if(dataResponse!=nil){
NSError *error;
dicUpdates = [NSJSONSerialization JSONObjectWithData:dataResponse options:NSJSONReadingMutableContainers error:&error];
}
if(dicUpdates.count > 0) isUpdateAvailable = YES;
else isUpdateAvailable = NO;
return isUpdateAvailable;
}
I need a synchronous request for this, cause the next view controller will be dependent on the server response. However sometimes it takes a long time for the server to respond or the internet is really slow, i need to set a time out to prevent the app from 'being frozen'.
I previously used NSUrlconnection to accomplish this task, but it has been deprecated.
Also, I tried using NSURLSession, (been using it also to download updates in the background thread), but i just can figure out if it can be used for a synchronous request.
Any idea how to deal with this? i just need a synchronous method that returns a BOOL. Best regards.
We have to use NSURLRequest in NSURLSession to set timeout interval.
Check below code:
- (BOOL)isUpdateAvailable{
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"url that returns json object"] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:4]//timeout
completionHandler:^(NSData *dataResponse,
NSURLResponse *response,
NSError *error) {
// handle response
if(dataResponse!=nil){
NSError *error;
dicUpdates = [NSJSONSerialization JSONObjectWithData:dataResponse options:NSJSONReadingMutableContainers error:&error];
}
if(dicUpdates.count > 0) isUpdateAvailable = YES;
else isUpdateAvailable = NO;
return isUpdateAvailable;
}] resume];
}

Unsupported URL on ios 8

I'm trying to fetch a json from google places with the following code:
NSString *query = [NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/place/search/json?location=%f,%f&radius=%i&types=%#&sensor=true&key=%#", center.latitude, center.longitude, rad, types, kGOOGLE_API_KEY];
NSLog(#"%#",query);
NSURL *googleRequestURL=[NSURL URLWithString:query];
[NSURLConnection sendAsynchronousRequest:[[NSURLRequest alloc] initWithURL:googleRequestURL] queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
NSLog(#"Error fetching data: %#",[error description]);
} else {
//To-do
}
}];
The resulting url is: https://maps.googleapis.com/maps/api/place/search/json?location=37.337566,-122.041202&radius=1000&types=accounting|bowling_alley|doctor&sensor=true&key=MY_KEY
(my key is ommitted for obsious reasons)
Which works fine from my laptop's browser, but return the error:
Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" UserInfo=0x7fe47bc138f0 {NSLocalizedDescription=unsupported URL, NSUnderlyingError=0x7fe47be9dbe0 "unsupported URL"}
I tried using http instead of https (in the browser it returns a json with some error message, but still returns something) with no success.
What am I doing wrong?
This is how I got it resolved. Good Luck!
NSString *google = #"https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=%f,%f&radius=500&types=%#&key=%#";
NSString *link = [NSString stringWithFormat:google, coordinate.latitude, coordinate.longitude, types, GOOGLE_KEY];
NSURL *url = [NSURL URLWithString:[link stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:url];

Xcode, UIImage is NIL when i trying to implement Load image asynchronously

I trying to implement Load image asynchronously.
NSURL *url = [NSURL URLWithString:_posterImg];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
UIImage *getImg = [[UIImage alloc] initWithData:data];
// do whatever you want with image
}
}];
But when i put this code, getImg will get warning "Unused Variable". i had check "response", "data" and "error", it all look fine but the getImg is NIL. is that i had write any thing wrong? thanks.
The affected variable is response. Although you use data and error, response is only declared as a parameter but nowhere used in your completion handler!
NSURL *url = [NSURL URLWithString:_posterImg];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
NSString *errorMsg = nil;
UIImage *getImg = nil;
if (!error){
getImg = [[UIImage alloc] initWithData:data];
}
else
{
errorMsg = [NSString stringWithFormat:#"Failed to load image. Error Message: %#", [error localizedString]];
}
[self handleImageRequestWithResponse:response image:getImg andErrorMessage:errorMsg];
}];
// Image hasn't load yet here since the request is asynchronously!
//if(getImg != nil && errorMsg == nil)
// NSLog(#"Image is available!");
//else
// NSLog(#"Loading the image asynchronously failed! %#", errorMsg);
// In addition now provide the following method.
- (void) handleImageRequestWithResponse:(NSURLResponse*)response image:(UIImage*)img andErrorMessage:(NSString*)err
{
if(img!= nil && err == nil)
NSLog(#"Image is available!");
else
NSLog(#"Loading the image asynchronously failed! %#", err);
// Handle image
};
EDIT: My bad! Since the code executes asynchronously getImg was of course nil when you checked as before
EDIT:
Using NSData dataWithContentsOfURL is synchronous,i.e. if executed on the main thread your application is blocked.
See this official documentation: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/Reference/Reference.html#//apple_ref/occ/clm/NSData/dataWithContentsOfURL:
Most important:
Important: Do not use this synchronous method to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
Going for a completionHandler and a handler method called after the requested raw data has been handled/prepared is better for your performance and does not violate the official recommendation!

Restkit 0.20 basic operation

I am just getting started with RestKit and have arrived just as Rk 0.20 is going live and the documentation and demo's are a step behind. Most stuff on the web is for RK 0.10 and there are big changes in the 0.20 version.
I don't want to fall back to an earlier version when the new one will very soon be up and running.
I have a JSON resource at a URL "test.myserver.com" that returns a simple datagram -
{
"id_user": "4401",
"datalocation": "4401",
"country": "Great-Britain",
"data": "testdata",
"login": "Fred Bloggs",
"password": "579c0cb0ed2dc25db121283f7a98cc71",
"accessLevel": "2",
"timestamp": "1012",
"datahash": "2749da29f20ce7a85092323f193adee8"
}
I am pretty sure I have the Mappings etc sorted but my service requires authentication so I need to pass a user name and password in the request to the server.
I have got this so far
NSURL *url = [NSURL URLWithString:#"http://test.myserver.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[ responseDescriptor ]];
[objectRequestOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"Load collection of Articles: %#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
[objectRequestOperation start];
Which appears to contact the server but inevitably logs the following error
restkit.network:RKObjectRequestOperation.m:296 Object request failed: Underlying HTTP request operation failed with error: Error Domain=org.restkit.RestKit.ErrorDomain Code=-1011 "Expected status code in (200-299), got 401" UserInfo=0x7884030 {NSLocalizedRecoverySuggestion={
"error": {
"code": 401,
"message": "Unauthorized: Authentication required"
}
}, AFNetworkingOperationFailingURLRequestErrorKey=http://elancovision.umfundi.com>, NSErrorFailingURLKey=http://elancovision.umfundi.com, NSLocalizedDescription=Expected status code in (200-299), got 401, AFNetworkingOperationFailingURLResponseErrorKey=}
The question of course is how I add the user name and password into the request.
Sorry for the noob question!
With basic HTTP authentication, username and password should be inserted into the HTTP request authorization header field for each request.
First, I suggest you to use RKObjectManager to centralize configuration for requests and mappings. http://restkit.org/api/latest/Classes/RKObjectManager.html
RKObjectManager can store network parameters (through AFNetworking Library), then build appropriate http query based on username/password, paths, objectmapping.
Adapting your example, it would give something like :
NSURL* url = [[NSURL alloc]initWithString:#"http://test.myserver.com"];
RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:url];
[objectManager.HTTPClient setAuthorizationHeaderWithUsername:#"username" password:#"password"];
//NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLRequest *request = [objectManager requestWithObject:nil method:RKRequestMethodGET path:#"/yourAPI/yourmethod" parameters:nil];
RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[ responseDescriptor ]];
[objectRequestOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"Load collection of Articles: %#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
[objectRequestOperation start];
If authentication works, having a look at the RESTKit wiki should give you the next hints to build correct mappings : https://github.com/RestKit/RestKit/wiki/Object-mapping
My solution here:
// Build a RestKit manager object to look after the restful stuff
RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"http://test.myserver.com"]];;
// Hash the GUI input password string and pass the username in plain text
NSString *md5PW = [umfundiCommon md5:passwordField.text];
[manager.HTTPClient setAuthorizationHeaderWithUsername:userField.text password:md5PW];
RKObjectMapping *WebResponse = [RKObjectMapping mappingForClass:[WSObject class]];
[WebResponse addAttributeMappingsFromDictionary:#{#"id_user":#"id_user", #"datalocation": #"datalocation", #"country":#"country", #"data": #"data", #"login": #"login", #"password": #"password", #"accessLevel": #"accessLevel", #"timestamp": #"timestamp", #"datahash": #"datahash"}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:WebResponse pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
// Add the above response descriptor to the manager
[manager addResponseDescriptor:responseDescriptor];
// the getObject makes the call using the stuff assembled into the manager Object and drops into either the success or the failure routines.
[manager getObject:nil path:#"" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *result)
{
NSLog (#"Server WS call success:");
NSArray *theresults = [result array];
for (WSObject *item in theresults) {
NSLog(#"datahash=%#",item.datahash);
NSLog(#"user_id=%#",item.id_user);
}
} failure:^(RKObjectRequestOperation * operation, NSError * error)
{
NSLog (#"Server WS call failure: operation: %# \n\nerror: %#", operation, error);
}];
........

NSURLConnection sendAsynchronousRequest:queue:completionHandler: doesn't call delegate method - didReceiveAuthenticationChallenge

I have a REST API which is secured by digest. I want to download my JSON response, but first I've to authenticate against the rest api.
I'm doing my Requests with sendAsynchronousRequest:queue:completionHandler:. But I don't know how to handle the digest authentication. I thought with the delegate method didReceiveAuthenticationChallenge of NSURLConnectionDelegate this should be possible? I've declared in the .h file the NSURLConnectionDelegate and added in the implementation the method. But nothing happens. Any advice how to handle this with "sendAsynchronousRequest:queue:completionHandler:" ?
NSURL *url = [NSURL URLWithString:#"http://restapi/"];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] > 0 && error == nil)
[self receivedData:data];
else
NSLog(#"error");
}];
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSLog(#"did get auth challenge"); }
The connection:didReceiveAuthenticationChallenge: will only be called if you specify your instance as the delegate of the connection. To do so you'll need to use a different method to start the request, e.g.:
NSURL *url = [NSURL URLWithString:#"http://restapi/"];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:urlRequest delegate:self]
You will need to implement further delegate methods in order to receive the response.
Note that connection:didReceiveAuthenticationChallenge: is deprecated in favor of other delegate methods (see this page).
Have a look at this question chain set might be this can help:
Authentication with NSURLConnection sendAsynchronousRequest with completion handler

Resources