How can I allow https connection using AFNetworking? - ios

I have AFNetworking set up but it is not accept https urls. How can I get AFNEtworking to connect via ssl.
I have the following code:
NSMutableURLRequest *apiRequest =
[self multipartFormRequestWithMethod:#"POST"
path: pathstr
parameters: params
constructingBodyWithBlock: ^(id <AFMultipartFormData>formData)
{
//TODO: attach file if needed
}];
AFJSONRequestOperation* operation = [[AFJSONRequestOperation alloc] initWithRequest: apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//success!
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure :(
NSLog(#"%#", error);
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
[operation start];

operation.securityPolicy.allowInvalidCertificates = YES;
This code is very important. If you dont add this you will get an error.

This will obviously only work if you have a non self-signed cert OR you add:
#define _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_ to your pch file. If you are using cocoa pods for this you will likely need to subclass AFHTTPRequestOperation and implement:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
if ([[protectionSpace authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self bypassSslCertValidation:protectionSpace])
return YES;
else
return [super connection:connection canAuthenticateAgainstProtectionSpace:protectionSpace];
}
return [super connection:connection canAuthenticateAgainstProtectionSpace:protectionSpace];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self bypassSslCertValidation:challenge.protectionSpace]) {
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
return;
}
else
return [super connection:connection didReceiveAuthenticationChallenge:challenge];
return;
}
}
- (BOOL) bypassSslCertValidation:(NSURLProtectionSpace *) protectionSpace
{
if (ENVIRONMENT_TYPE == DEV_ENV || ENVIRONMENT_TYPE == STAGING_ENV) {
return YES;
}
return NO;
}
Then tell AFNEtworking to use the new subclass:
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#""]];
[client registerHTTPOperationClass:[YourSubClassHTTPRequestOperation class]];
It's not the easiest thing to do in the world and technically ignoring self-signed isn't making it work, but if you use standard SLL certificates It's probable it will work just fine, remember to remove this code or make it only available when debugging if you plan to release.
Adding to answer because comments have char limits!
Few choices looking at the headers
Return operation that can be manually added to the queue:
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
Or pass in your custom subclass operation to this one:
- (void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation;

Try this code.
NSMutableURLRequest *apiRequest =
[self multipartFormRequestWithMethod:#"POST"
path: pathstr
parameters: params
constructingBodyWithBlock: ^(id <AFMultipartFormData>formData)
{
//TODO: attach file if needed
}];
AFJSONRequestOperation* operation = [[AFJSONRequestOperation alloc] initWithRequest: apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//success!
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure :(
NSLog(#"%#", error);
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
operation.securityPolicy.allowInvalidCertificates = YES;
[operation start];

If AFNetworking is disabled subclass over superclass implementation then simply put in code like :http//foo//bar and set this to bool

Related

Use result from asynchronous operation iOS

Hello I want to get result from asynchronous block from the class DataViewControllerto use it in the class MagazineViewControllerbut I don't get the value of returnedDataunless the ViewDidLoadmethod is called for the second time or more as the operation is asynchronous, I know that I have to implement a completion block and call it but I don't know exactly how to do it, this is my code, how can I edit it to give me the value returned in the setCompletionBlockWithSuccessblock and use it in other classes
DataViewController.m
- (void)getDataFromServer:(NSString *)urlString completion:(void (^)(AFHTTPRequestOperation *operation, id responseObject))completion {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSURLCredential *credential = [NSURLCredential credentialWithUser:userName password:Password persistence:NSURLCredentialPersistenceNone];
NSMutableURLRequest *request = [manager.requestSerializer requestWithMethod:#"GET" URLString:urlString parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCredential:credential];
[operation setResponseSerializer:[AFJSONResponseSerializer alloc]];
[operation setCompletionBlockWithSuccess:completion failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", error);
} ];
[manager.operationQueue addOperation:operation];
}
The MagazineViewController class (Where I want to use the returnedData)
#implementation MagazineViewController
void (^completion)(AFHTTPRequestOperation* , id) = ^(AFHTTPRequestOperation *operation, id responseObject){
returnedData = responseObject;};
- (void)viewDidLoad {
[super viewDidLoad];
DataViewController *dataViewController = [[DataViewController alloc] init];
[dataViewController getDataFromServer:#"http://firstluxe.com/api/search/search?query=vogue&language=1&output_format=JSON" completion:completion];
NSLog(#"returned %# ", returnedData); // here I get the value after the view controller is loaded for the second time or more
You should define completion inline. Your first NSLog statement is called before completion has been called, so the returnedData variable has not been set.
#implementation MagazineViewController
- (void)viewDidLoad {
[super viewDidLoad];
DataViewController *dataViewController = [[DataViewController alloc] init];
[dataViewController getDataFromServer:#"http://firstluxe.com/api/search/search?query=vogue&language=1&output_format=JSON"
completion:^ (AFHTTPRequestOperation *operation, id responseObject) {
returnedData = responseObject;
NSLog(#"returned %# ", returnedData);
}];
}
#end
[self getDataFromServer:#"API URL" completion:^(int *operation, id responseObject) {
// Result from async method here.
}];

JSON Request using AFNetworking 2 giving me a NSURLErrorDomain error

I am making a simple GET request using AFNetworking 2, but I am getting a NSURLErrorDomain error.
I created a manager class which subclasses AFHTTPRequestOperationManager and creates a singleton instance so that I can use a shared manager.
+ (id)manager {
static dispatch_once_t pred = 0;
__strong static id _sharedObject = nil;
dispatch_once(&pred, ^{
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
- (id)init {
NSURL *baseURL = [ZSSAuthentication baseURL];
self = [super initWithBaseURL:baseURL];
if (self) {
[self setRequestSerializer:[AFJSONRequestSerializer serializer]];
[self setResponseSerializer:[AFJSONResponseSerializer serializer]];
[self.requestSerializer setAuthorizationHeaderFieldWithUsername:[ZSSAuthentication username] password:[ZSSAuthentication password]];
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
}
return self;
}
- (void)getData:(NSString *)pubID parameters:(NSDictionary *)parameters completion:(void (^)(NSDictionary *results))completion failure:(void (^)(NSError *error))failure {
NSString *url = [NSString stringWithFormat:#"data/all/%#", pubID];
[self GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
// Check to see if there are errors
ZSSError *error = [self errorForAPICall:responseObject status:[operation.response statusCode]];
if (error) {
[self logMessage:error.localizedDescription];
failure(error);
return;
}
NSDictionary *data = [responseObject objectForKey:#"data"];
completion(data);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
}];
}
Then, in my viewController's viewDidLoad method I make a call to that method:
[[ZSSManager manager] getData:self.pubID parameters:nil completion:^(NSDictionary *results) {
self.items = results;
[self dataWillReload];
NSLog(#"%#", results);
[self.tableView reloadData];
} failure:^(NSError *error) {
NSLog(#"Error: %# %li", error, (long)error.code);
}];
Then I get this error:
Error Domain=NSURLErrorDomain Code=-999 "The operation couldn’t be completed. (NSURLErrorDomain error -999.)" UserInfo=0x7ff952306610 {NSErrorFailingURLKey=http://test.mysite.com/v1/data/all/5}
The strange thing is, on a previous viewController, I make a different call to the manager, and it completes and returns data correctly. But, when I make this second call, I get the error. AND, if I move that getData call out of the viewDidLoad method, and invoke it with a button press, it DOES WORK. What the heck?
What could be causing this?

How to waiting asynchronous NSURLConnection response

I am trying to fill a UITableView by fetching an URL. To avoid freeze UI I using dispatch_async(dispatch_queue_t queue, ^(void)block) making asynchronous fetching.
In the asynchronous block using NSRunLoop to waiting response.
Here's my fetching code:
- (BOOL)isFinished {
return _finished;
}
- (void)startReceive {
NSURLRequest* request = [NSURLRequest requestWithURL:self.baseURL];
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
assert(self.connection);
}
- (void)stopReceiveWithStatus:(NSString*)statusCode {
if (self.connection) {
[self.connection cancel];
self.connection = nil;
}
self.finished = YES;
NSLog(#"Stop Receive with status code %#", statusCode);
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
assert(connection == self.connection);
NSHTTPURLResponse* httpResponse = nil;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
httpResponse = (NSHTTPURLResponse*) response;
if (httpResponse.statusCode != 409) {
// I need 409 to fetch some data
[self stopReceiveWithStatus:[NSString stringWithFormat:#"HTTP error %2d", httpResponse.statusCode]];
}
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
assert(connection == self.connection);
self.sessionID = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
assert(connection == self.connection);
[self stopReceiveWithStatus:#"Connection failed"];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
assert(connection == self.connection);
[self stopReceiveWithStatus:nil];
}
UITableViewController:
// refreshControl handler
- (IBAction)refresh {
[self startRefreshingAnimation];
dispatch_queue_t loaderQ = dispatch_queue_create("loading transmission data", NULL);
dispatch_async(loaderQ, ^{
[self foo];
while(!self.t.finished) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self endRefreshingAnimation]; // end
});
});
}
- (void)foo {
NSLog(#"session id: %#", self.t.sessionID);
}
That sessionID will be filled when HTTP request has done. Now the problem is I can not get the sessionID when first call because the first time it not be filled and second time it works good. How can I get it when first call?
I'm new to iOS. If you have a better solution to solve this problem please tell me, thanks.
There is a good framework, don't waste your time.
https://github.com/AFNetworking/AFNetworking/wiki/Getting-Started-with-AFNetworking
Upd.
You can do your request like:
NSURL *url = self.baseURL;
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
height, #"user[height]",
weight, #"user[weight]",
nil];
[httpClient postPath:#"/myobject" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *responseStr = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSLog(#"Request Successful, response '%#'", responseStr);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"[HTTPClient Error]: %#", error.localizedDescription);
}];
You should use an third-party library to handle network connections, such as STHTTPRequest:
STHTTPRequest *r = [STHTTPRequest requestWithURLString:#"http://www.apple.com"];
r.completionBlock = ^(NSDictionary *headers, NSString *body) {
// ...
};
r.errorBlock = ^(NSError *error) {
// ...
};
[r startAsynchronous];

AFNetworking POST Never Succeeding - Unexpected Behavior Occurring on Different Devices

I am currently developing an iOS app that allows a user to post a classified listing for other users to see. On all devices, that app hangs in a "O% Uploaded" SVProgressHUD for a while before displaying "Our server is temporarily unavailable. Please try again later.", which is what I have coded in the case of a Status Code 500 error from the server side. However, on the iOS simulator, everything works smoothly.
All other functionality involving network requests is working perfectly except for this, so it must have something to do with the actual upload process. I've posted the corresponding code below; if you require anything additional to help figure this out, please let me know.
Networking Code
- (IBAction)publishPressed:(UIBarButtonItem *)sender {
[SVProgressHUD showWithStatus:#"Loading..." maskType:SVProgressHUDMaskTypeBlack];
// Set the params for the API call in an NSMutableDictionary called mutableParams.
// Create the form request with the post parameters and image data to pass to the API.
NSMutableURLRequest *request = [[UAPIClient sharedClient] multipartFormRequestWithMethod:#"POST"
path:#"mobPost.php"
parameters:mutableParams
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
if (self.imageHasBeenSet) {
[self.listingImageView.image fixOrientation];
NSData *imageData = UIImagePNGRepresentation(self.listingImageView.image);
[formData appendPartWithFileData:imageData name:#"userfile"
fileName:#"postImage.png" mimeType:#"image/png"];
}
}];
// Create the `AFJSONRequestOperation` from the form request, with appropriate success and failure blocks.
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseJSON) {
if ([[responseJSON objectForKey:#"status"] intValue] == 1) {
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showSuccessWithStatus:#"Success!"];
});
// Pass a message back to the delegate so that the modal view controller
// can be dismissed successfully.
[self.delegate uCreateListingTableViewController:self didCreateListing:YES];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showErrorWithStatus:#"Post failed. Please try again."];
});
NSLog(#"Status %#", [responseJSON objectForKey:#"status"]);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showErrorWithStatus:#"Our server is temporarily unavailable. Please try again later."];
});
}];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showSuccessWithStatus:[[NSString stringWithFormat:#"%lli", (totalBytesWritten / totalBytesExpectedToWrite)] stringByAppendingString:#"% Uploaded"]];
});
}];
[[UAPIClient sharedClient] enqueueHTTPRequestOperation:operation];
}
UAPIClient.m
#import "UAPIClient.h"
#import "AFJSONRequestOperation.h"
static NSString * const kUAPIBaseURLString = #"hiding string for privacy";
#implementation UAPIClient
+ (UAPIClient *)sharedClient {
static UAPIClient *_sharedClient;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[UAPIClient alloc] initWithBaseURL:[NSURL URLWithString:kUAPIBaseURLString]];
});
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (self) {
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Accept" value:#"application/json"];
}
return self;
}
#end

401 error - Basic Authentication with AFNetworking AFHTTPClient & Tastypie

I'm using AFHTTPClient from AFNetworking to make a call from my IOS app to my server, which is using Django with TastyPie. It's working great when I turn authentication off on the server side; however, when I require authentication and insert the proper username and password into my code, the I receive the following 401 authentication error:
\2012-09-16 00:24:37.877 RESTtest[76909:f803]
Complex AF: Error Domain=AFNetworkingErrorDomain Code=-1011
"Expected status code in (200-299), got 401"
UserInfo=0x686ba00 {AFNetworkingOperationFailingURLResponseErrorKey=<NSHTTPURLResponse: 0x686f130>,
NSErrorFailingURLKey=http://127.0.0.1:8000/api/v1/shoppinglist,
NSLocalizedDescription=Expected status code in (200-299), got 401,
AFNetworkingOperationFailingURLRequestErrorKey=<NSMutableURLRequest http://127.0.0.1:8000/api/v1/shoppinglist>}
Here is my code:
AFAPIClient.h
#import "AFHTTPClient.h"
#interface AFAPIClient : AFHTTPClient
-(void)setUsername:(NSString *)username andPassword:(NSString *)password;
+ (AFAPIClient *)sharedClient;
#end
AFAPIClient.m:
#import "AFAPIClient.h"
#import "AFJSONRequestOperation.h"
static NSString * const baseURL = #"http://#127.0.0.1:8000/api/v1";
#implementation AFAPIClient
+ (AFAPIClient *)sharedClient {
static AFAPIClient *_sharedClient = nil;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
_sharedClient = [[AFAPIClient alloc] initWithBaseURL:[NSURL URLWithString:baseURL]];
//[_sharedClient setAuthorizationHeaderWithUsername:#"myusername" password:#"mypassword"]; I tried putting the authorization command here
});
return _sharedClient;
};
#pragma mark - Methods
-(void)setUsername:(NSString *)username andPassword:(NSString *)password;
{
[self clearAuthorizationHeader];
[self setAuthorizationHeaderWithUsername:username password:password];
}
- (id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setParameterEncoding:AFJSONParameterEncoding];
//[self setAuthorizationHeaderWithUsername:#"myusername" password:#"mypassword"]; I also tried putting the authorization command here
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Accept" value:#"application/json"];
return self;
}
#end
TQViewController.h:
[...]
- (IBAction)sendAFClientRequest:(id)sender {
//[[AFAPIClient sharedClient] setUsername:#"myusername" andPassword:#"mypassword"];
[[AFAPIClient sharedClient] getPath:#"shoppinglist" parameters:nil success:^(AFHTTPRequestOperation *operation, id response) {
NSLog(#"Complex AF: %#", [response valueForKeyPath:#"objects"]);
} failure:^(AFHTTPRequestOperation *operation, id response) {
NSLog(#"Complex AF: %#", response);
}
];
}
[...]
I know this isn't a problem with my server or my username/password, as I can authenticate just fine by inserting the username/password into the URL:
#"http://myusername:mypassword#127.0.0.1:8000/api/v1/shoppinglist"
Any help on this would be great. It would be wonderful to be able to use AFHTTPClient without inserting the authentication information directly into the static base URL, which seems completely improper. Thanks in advance!
Based on this: https://github.com/AFNetworking/AFNetworking/issues/426
I override the - (void)getPath:(NSString *)path parameters... method in the AFHTTPClient
subclass to look something like this:
- (void)getPath:(NSString *)path parameters:(NSDictionary *)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
NSURLRequest *request = [self requestWithMethod:#"GET" path:path parameters:parameters];
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];
[operation setAuthenticationChallengeBlock:^(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge) {
NSURLCredential *newCredential = [NSURLCredential credentialWithUser:self.username password:self.password persistence:NSURLCredentialPersistenceForSession];
[challenge.sender useCredential:newCredential forAuthenticationChallenge:challenge];
}];
[self enqueueHTTPRequestOperation:operation];
}
It only adds the authentication challenge block to the AFHTTPRequestOpertaion, the rest is the same as the original implementation https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/AFHTTPClient.m

Resources