Skip to content

Instantly share code, notes, and snippets.

@jkereako
Last active August 29, 2015 14:14
Show Gist options
  • Save jkereako/70df01d95019ae2e9765 to your computer and use it in GitHub Desktop.
Save jkereako/70df01d95019ae2e9765 to your computer and use it in GitHub Desktop.
Classes for handling HTTP requests and credential storage.
//
// ADAuthenticationModel.h
// NetworkAuthentication
//
// Created by Jeffrey Kereakoglow on 3/11/15.
// Copyright (c) 2015 Alexis Digital. All rights reserved.
//
@import Foundation;
@interface ADAuthenticationModel : NSObject
@property (nonatomic) NSString *username;
@property (nonatomic) NSString *password;
@property (nonatomic) NSString *authenticationToken;
@property (nonatomic) NSURLCredential *credential;
@property (nonatomic, readonly) NSString *urlString;
@property (nonatomic, readonly) NSURLProtectionSpace *urlProtectionSpace;
@property (nonatomic, getter=isAuthenticated) BOOL authenticated;
- (instancetype)initWithURLString:(NSString *)urlString __attribute((nonnull)) NS_DESIGNATED_INITIALIZER;
- (NSString *)hashedStringFromString:(NSString *)stringToHash __attribute((nonnull));
@end
//
// ADAuthenticationModel.m
// NetworkAuthentication
//
// Created by Jeffrey Kereakoglow on 3/11/15.
// Copyright (c) 2015 Alexis Digital. All rights reserved.
//
#import "ADAuthenticationModel.h"
#import <CommonCrypto/CommonDigest.h>
@interface ADAuthenticationModel ()
- (NSURLCredential *)credentialFromKeychainForProtectionSpace:(NSURLProtectionSpace *)protectionSpace __attribute((nonnull));
@end
@implementation ADAuthenticationModel
@synthesize credential = _credential;
- (instancetype)initWithURLString:(NSString *)urlString {
self = [super init];
if (self) {
_urlString = urlString;
}
return self;
}
#pragma mark - Getters
- (NSString *)username {
NSAssert(self.credential, @"\n\n ERROR in %s: The property \"_credential\" is nil.\n\n", __PRETTY_FUNCTION__);
return self.credential.user;
}
- (NSString *)password {
NSAssert(self.credential, @"\n\n ERROR in %s: The property \"_credential\" is nil.\n\n", __PRETTY_FUNCTION__);
return self.credential.password;
}
- (NSURLProtectionSpace *)urlProtectionSpace {
NSAssert(self.urlString, @"\n\n ERROR in %s: The property \"_urlString\" is nil.\n\n", __PRETTY_FUNCTION__);
static NSURLProtectionSpace *_urlProtectionSpace;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURL *url = [NSURL URLWithString:self.urlString];
if (url) {
_urlProtectionSpace = [[NSURLProtectionSpace alloc] initWithHost:url.host
port:[url.port integerValue]
protocol:url.scheme
realm:nil
authenticationMethod:NSURLAuthenticationMethodDefault];
}
else {
NSAssert(NO, @"\n\n UNEXPECTED BEHAVIOR in %s", __PRETTY_FUNCTION__);
}
});
return _urlProtectionSpace;
}
/**
Fetch an NSURLCredential from the keychain and set it——it's unorthodox to set a property in a getter; I'm aware——to the property, _credential.
@returns NSURLCredential * or nil
@since 1.0
@author Jeff Kereakoglow
*/
- (NSURLCredential *)credential {
// Normal getter behavior
if (_credential) {
return _credential;
}
// Fetch the credential from the keychain, if it exists.
return [self credentialFromKeychainForProtectionSpace:self.urlProtectionSpace];
}
#pragma mark - Setters
/**
Fetch an NSURLCredential from the keychain and set it to the property _credential.
@param credential NSURLCredential *
@since 1.0
@author Jeff Kereakoglow
*/
- (void)setCredential:(NSURLCredential *)credential {
NSAssert(credential, @"\n\n ERROR in %s: The parameter \"credential\" is nil.\n\n", __PRETTY_FUNCTION__);
NSAssert(self.urlProtectionSpace, @"\n\n ERROR in %s: The property \"_urlProtectionSpace\" is nil.\n\n", __PRETTY_FUNCTION__);
// Assign the credential to the Keychain
[[NSURLCredentialStorage sharedCredentialStorage] setCredential:credential
forProtectionSpace:self.urlProtectionSpace];
_credential = credential;
}
#pragma mark - Private helpers
/**
Hashes a string using SHA256.
@param stringToHash NSString *
@returns The hashed string
@since 1.0
@see https://github.com/mdznr/iOS-Passcode/blob/master/Passcode/NSString%2Bsha256.m#L61-L75
@author Jeff Kereakoglow
*/
- (NSString *)hashedStringFromString:(NSString *)stringToHash {
NSMutableString *hashedString;
const char *str = [stringToHash UTF8String];
unsigned char result[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(str, (CC_LONG) strlen(str), result);
hashedString = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
for (NSUInteger i = 0; i != CC_SHA256_DIGEST_LENGTH; ++ i) {
[hashedString appendFormat:@"%02x",result[i]];
}
return [NSString stringWithString:hashedString];
}
/**
Fetches from the keychian a single @c NSURLCredential for a given @c NSURLProtectionSpace.
@param protectionSpace NSURLProtectionSpace *
@returns The @c NSURLCredential or nil if no such credential exists
@since 1.0
@see https://github.com/mdznr/iOS-Passcode/blob/master/Passcode/NSString%2Bsha256.m#L61-L75
@author Jeff Kereakoglow
*/
- (NSURLCredential *)credentialFromKeychainForProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
NSURLCredential *credential;
NSDictionary *credentials = [[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:protectionSpace];
if (credentials.count) {
credential = [credentials.objectEnumerator nextObject];
if (!credential) {
NSAssert(NO, @"\n\n UNEXPECTED BEHAVIOR in %s.\n\n", __PRETTY_FUNCTION__);
}
}
return credential;
}
@end
//
// ADWebService.h
// NetworkAuthentication
//
// Created by Jeffrey Kereakoglow on 3/10/15.
// Copyright (c) 2015 Alexis Digital. All rights reserved.
//
@import Foundation;
@interface ADWebService : NSObject<NSURLSessionDataDelegate>
+ (instancetype)webServiceWithURLString:(NSString *)urlString __attribute((nonnull));
@property (nonatomic, readonly) NSURL *url;
@property (nonatomic, readonly) NSURLProtectionSpace *urlProtectionSpace;
@property (nonatomic, readonly) NSURLCredential *urlCredential;
- (id)collectionFromJSONData:(NSData *)data error:(__autoreleasing NSError **)error __attribute((nonnull));
- (BOOL)sendHTTPFormData:(NSData *)formData completionHandler:(void (^)(NSURLRequest *request, id response, NSError *error))completionHandler __attribute((nonnull));
- (BOOL)sendJSONData:(NSData *)jsonData completionHandler:(void (^)(NSURLRequest *request, id response, NSError *error))completionHandler __attribute((nonnull));
@end
//
// ADWebService.m
// NetworkAuthentication
//
// Created by Jeffrey Kereakoglow on 3/10/15.
// Copyright (c) 2015 Alexis Digital. All rights reserved.
//
#import "ADWebService.h"
@import UIKit.UIApplication;
static NSURLSession *urlSession;
@interface ADWebService ()
@property (nonatomic, readonly) NSMutableURLRequest *mutableURLRequest;
- (instancetype)initWithURLString:(NSString *)urlString __attribute((nonnull)) NS_DESIGNATED_INITIALIZER;
- (BOOL)fetchResourceWithQueryString:(NSString *)queryString completionHandler:(void (^)(NSURLRequest *request,id response, NSError *error))completionHandler __attribute((nonnull));
- (BOOL)sendData:(NSData *)data contentType:(NSString *)contentType completionHandler:(void (^)(NSURLRequest *request, id response, NSError *error))completionHandler __attribute((nonnull));
- (void)handleDataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *request, id response, NSError *error))completionHandler;
@end
@implementation ADWebService
#pragma mark -
+ (instancetype)webServiceWithURLString:(NSString *)urlString {
ADWebService *instance = [[[self class] alloc] initWithURLString:urlString];
if(!urlSession) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
// Customize NSURLSession
config.URLCredentialStorage = [NSURLCredentialStorage sharedCredentialStorage];
config.allowsCellularAccess = NO;
// NSURLSessionConfiguration defaults. These must not be overridden
config.timeoutIntervalForRequest = 30.0;
config.timeoutIntervalForResource = 60.0;
// This limit is per-session. Keep it high so we can make concurrent
// connections on multiple threads.
config.HTTPMaximumConnectionsPerHost = 5;
urlSession = [NSURLSession sessionWithConfiguration:config
delegate:instance
delegateQueue:nil];
}
return instance;
}
- (instancetype)initWithURLString:(NSString *)urlString {
self = [super init];
if (self) {
_url = [NSURL URLWithString:urlString];
_urlProtectionSpace = [[NSURLProtectionSpace alloc] initWithHost:_url.host
port:[_url.port integerValue]
protocol:_url.scheme
realm:nil
authenticationMethod:NSURLAuthenticationMethodDefault];
}
return self;
}
#pragma mark - Getters
- (NSURLCredential *)urlCredential {
NSAssert(self.urlProtectionSpace, @"\n\n ERROR in %s: The property \"_urlProtectionSpace\" is nil.\n\n", __PRETTY_FUNCTION__);
NSURLCredential *urlCredential;
NSDictionary *credentials;
credentials = [[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:self.urlProtectionSpace];
urlCredential = [credentials.objectEnumerator nextObject];
#if DEBUG
NSLog(@"\n\n INFO: Username: %@\n Password: %@.\n\n", urlCredential.user, urlCredential.password);
#endif
return urlCredential;
}
- (NSMutableURLRequest *)mutableURLRequest {
NSAssert(self.url, @"\n\n ERROR in %s: The property \"_url\" is nil.\n\n", __PRETTY_FUNCTION__);
NSMutableURLRequest *mutableURLRequest;
mutableURLRequest = [NSMutableURLRequest requestWithURL:self.url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[mutableURLRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"];
[mutableURLRequest setHTTPMethod:@"GET"];
return mutableURLRequest;
}
#pragma mark -
- (id)collectionFromJSONData:(NSData *)data error:(__autoreleasing NSError **)error {
id result = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:error];
return result;
}
#pragma mark - NSURLSessionTaskDelegate
// Handle authentication challenges.
// This delegate will be invoked happen for every TLS connection which has not
// been verified by a certificate authority (CA).
- (void)URLSession:(__unused NSURLSession *)session didReceiveChallenge:(__unused NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
// Allow connection if conditions are met.
if ([challenge.protectionSpace.host isEqualToString:self.urlProtectionSpace.host]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
// Deny connection
else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
#pragma mark - NFWebServiceDelegate
- (BOOL)sendJSONData:(NSData *)jsonData completionHandler:(void (^)(NSURLRequest *request, id response, NSError *error))completionHandler {
return [self sendData:jsonData
contentType:@"application/json"
completionHandler:completionHandler];
}
- (BOOL)sendHTTPFormData:(NSData *)formData completionHandler:(void (^)(NSURLRequest *request, id response, NSError *error))completionHandler {
return [self sendData:formData
contentType:@"application/x-www-form-urlencoded"
completionHandler:completionHandler];
}
#pragma mark -
- (BOOL)sendData:(NSData *)data contentType:(NSString *)contentType completionHandler:(void (^)(NSURLRequest *request, id response, NSError *error))completionHandler {
NSMutableURLRequest *mutableURLRequest = self.mutableURLRequest;
[mutableURLRequest setHTTPMethod:@"POST"];
[mutableURLRequest setValue:contentType
forHTTPHeaderField:@"Content-Type"];
[mutableURLRequest setHTTPBody:data ];
[self handleDataTaskWithRequest:mutableURLRequest
completionHandler:completionHandler];
return YES;
}
- (BOOL)fetchResourceWithQueryString:(__unused NSString *)queryString completionHandler:(void (^)(NSURLRequest *request, id response, NSError *error))completionHandler {
NSMutableURLRequest *mutableURLRequest = self.mutableURLRequest;
if (queryString) {
mutableURLRequest.URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",
self.url, queryString]];
}
[self handleDataTaskWithRequest:mutableURLRequest
completionHandler:completionHandler];
return YES;
}
- (void)handleDataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *request, id response, NSError *error))completionHandler {
NSAssert(urlSession, @"\n\n ERROR in %s: The property \"_urlSession\" is nil.\n\n", __PRETTY_FUNCTION__);
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[[urlSession dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
// If there was an error, then report it and execute the completion
// handler
if (error) {
completionHandler(request, response, error);
}
// Else, process the response.
else {
NSError *newError;
NSDictionary *vessels;
NSString *errorDescription;
NSInteger statusCode = 0;
// Sanity check
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
// Get the HTTP status code
statusCode = ((NSHTTPURLResponse *)response).statusCode;
switch (statusCode) {
case 200:
break;
case 400:
errorDescription = NSLocalizedString(@"webService.httpResponseCode.400.badRequest", @"HTTP: 400");
break;
case 401:
errorDescription = NSLocalizedString(@"webService.httpResponseCode.401.unauthorizedAccess", @"HTTP: 401");
break;
case 403:
errorDescription = NSLocalizedString(@"webService.httpResponseCode.403.forbidden", @"HTTP: 403");
break;
case 404:
errorDescription = NSLocalizedString(@"webService.httpResponseCode.404.notFound", @"HTTP: 404");
break;
case 500:
errorDescription = NSLocalizedString(@"webService.httpResponseCode.500.internalServerError", @"HTTP: 500");
break;
default:
NSAssert(NO, @"\n\n UNEXEPECTED BEHAVIOR in %s\n\n", __PRETTY_FUNCTION__);
break;
}
if (errorDescription) {
vessels = @{ NSLocalizedDescriptionKey : errorDescription};
newError = [NSError errorWithDomain:[NSBundle mainBundle].bundleIdentifier
code:statusCode
userInfo:vessels];
}
}
// If an error was generated from an HTTP status code, then call
// the completion handler
if (newError) {
completionHandler(request, response, newError);
}
// Else, process the response from the server normally.
else {
id result = [self collectionFromJSONData:data
error:&newError];
// Was there an error deserialized JSON?
if (newError) {
completionHandler(request, response, newError);
}
// Everything went smoothly.
else {
completionHandler(request, result, newError);
}
}
}
}] resume];
}
@end
@jkereako
Copy link
Author

Description

These are 2 classes which I use to handle HTTP requests and to store credentials.

  • ADWebService: Handles HTTP requests. It also includes helpers for sending form data or JSON data.
  • ADAuthenticationModel: Encapsulates routines related to credential storage. This is application specific. It's likely that you will not be able to use this class as-is.

Previously, I used AFNetworking to handle my networking needs, but since my discovery of NSURLSession, I've abandoned AFNetworking. NSURLSession is really, really easy to use compared to NSURLRequest.

Usage

This is meant to be used with another class called WebServiceClient whose sole purpose is to construct web requests in the format expected by the server.

The client is application specific. For example, you would create TwitterWebServiceClient which would contain methods specific to the Twitter API and then pass the object to the web service to actually make the call.

Credit

I got the idea of separating the web service and web service client from Objc.io Issue #10. In that issue, there exists the class PodsWebService and it's client class, Importer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment