Project import
diff --git a/Courier/CRSessionController.h b/Courier/CRSessionController.h
new file mode 100644
index 0000000..c82ac11
--- /dev/null
+++ b/Courier/CRSessionController.h
@@ -0,0 +1,204 @@
+//
+//  CRSessionController.h
+//  Courier
+//
+//  Created by Andrew Smith on 9/22/13.
+//  Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+#import <Reachability/Reachability.h>
+
+/**
+ The CRSessionController wraps around an NSURLSession. Use this class to create and managed tasks. For
+ instance, you can group tasks by name and bulk cancel said group.
+ */
+NS_CLASS_AVAILABLE(10_9, 7_0)
+@interface CRSessionController : NSObject
+
+/**
+ The internal NSURLSession.
+ */
+@property (nonatomic, readonly, strong) NSURLSession *session;
+
+/**
+ The NSOperationQueue on which the NSURLSessions delegate callbacks are run.
+ */
+@property (nonatomic, readonly, strong) NSOperationQueue *sessionDelegateOperationQueue;
+
+/**
+ A copy of the internal NSURLSessionConfiguration from the internal NSURLSession.
+ */
+@property (nonatomic, readonly) NSURLSessionConfiguration *configuration;
+
+/**
+ Create a session controller with the given NSURLSessionConfiguration. Note that once a configuration
+ is passed in, it is immutable.
+ */
+- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
+
+/**
+ Create a session controller with the given NSURLSessionConfiguration. Note that once a configuration
+ is passed in, it is immutable.
+ */
++ (instancetype)sessionControllerWithConfiguration:(NSURLSessionConfiguration *)configuration;
+
+/**
+ Create an NSURLSessionDataTask for the given request. The completion handler will run regardless of
+ whether or not the task succeeds or not.
+*/
+- (NSURLSessionDataTask *)dataTaskForRequest:(NSURLRequest *)request
+                           completionHandler:(void (^)(NSData *data,
+                                                       NSURLResponse *response,
+                                                       BOOL cachedResponse,
+                                                       NSError *error))completionHandler;
+/**
+ Create an NSURLSessionDataTask for the given request and add it to the specified
+ non-nil task group. If task group is nil, task will be added to generic group.
+ */
+- (NSURLSessionDataTask *)dataTaskForRequest:(NSURLRequest *)request
+                                   taskGroup:(NSString *)group
+                           completionHandler:(void (^)(NSData *data,
+                                                       NSURLResponse *response,
+                                                       BOOL cachedResponse,
+                                                       NSError *error))completionHandler;
+
+/**
+ Adds an NSURLSessionTaskDelegate to the given task. Right now, just the HTTPRedirect call and 
+ authentication challenge is passed through to the delegate, but future work will pass through 
+ all callbacks.
+ 
+ @param delegate The delegate that will respond to NSURLSessionTaskDelegate callbacks
+ @param task The task to use the delegate with.
+ */
+- (void)addNSURLSessionTaskDelegate:(id <NSURLSessionTaskDelegate>)delegate
+                            forTask:(NSURLSessionTask *)task;
+
+/**
+ Removes the delegate. At this point, the delegate will no longer respond to callbacks.
+
+ @param delegate The delegate to remove.
+ */
+- (void)removeNSURLSessionTaskDelegate:(id <NSURLSessionTaskDelegate>)delegate;
+
+/**
+ @returns The delegate specified in addNSURLSessionTaskDelegate:forTask: by the task parameter.
+
+ @param task The NSURLSessionTask to retrieve the delegate for.
+ */
+- (id <NSURLSessionTaskDelegate>)NSURLSessionTaskDelegateForTask:(NSURLSessionTask *)task;
+
+@end
+
+@interface CRSessionController (TaskManagement)
+
+/**
+ *  Returns the task, if any, associated with the task ID
+ *
+ *  @param taskId The task ID, called from task.taskIdentifier.
+ *
+ *  @return The task associated with the taskIdentifier
+ */
+- (NSURLSessionTask *)taskWithIdentifier:(NSUInteger)taskIdentifier;
+
+/**
+ *  A quick way to find out if you have a task with the given identifier.
+ *
+ *  @param taskIdentifier The task identifier, from task.taskIdentifier
+ *
+ *  @return Returns YES if the controller has a task with the given taskIdentifier.
+ */
+- (BOOL)hasTaskWithIdentifier:(NSUInteger)taskIdentifier;
+
+/**
+ @returns YES if the task group has any task with the state.
+ @param group The name of the task group.
+ @param state The state of the NSURLSessionTask to test against.
+ */
+- (BOOL)hasTasksInGroup:(NSString *)group
+              withState:(NSURLSessionTaskState)state;
+
+/**
+ Calls -suspend on all tasks in a given group.
+ */
+- (void)suspendTasksInGroup:(NSString *)group;
+
+/**
+ Calls -resume on all tasks in a given group.
+ */
+- (void)resumeTasksInGroup:(NSString *)group;
+
+/**
+ Calls -cancel on all tasks in a given group.
+ */
+- (void)cancelTasksInGroup:(NSString *)group;
+
+/**
+ Calls -suspend on all tasks tracked by this controller
+ */
+- (void)suspendAllTasks;
+
+/**
+ Calls -resume on all tasks tracked by this controller
+ */
+- (void)resumeAllTasks;
+
+/**
+ Calls -cancel on all tasks tracked by this controller
+ */
+- (void)cancelAllTasks;
+
+@end
+
+@interface CRSessionController (Reachability)
+
+/**
+ Returns YES if any internet connection is reachable.
+ */
+- (BOOL)isInternetReachable;
+
+/**
+ Starts monitoring for reachability changes. If there is a change, the reachability status block will
+ be called. Alternatively, you can listen for the kReachabilityChangedNotification NSNotification.
+ */
+- (void)startReachabilityMonitoring;
+
+/**
+ Stops reachability monitoring. Changes in reachability will no longer call the reachability status
+ change block, or fire off the kReachabilityChangedNotification NSNotification.
+ */
+- (void)stopReachabilityMonitoring;
+
+/**
+ Sets a block that will run every time the network reachability status changes.
+ @param block The block to call when the network status changes.
+ */
+- (void)setReachabilityStatusChangeBlock:(void (^)(NetworkStatus status))block;
+
+@end
+
+@interface CRSessionController (Debug)
+
+/**
+ *  Pretty prints all the current requests both queued and executing
+ */
+- (void)logRequests;
+
+@end
diff --git a/Courier/CRSessionController.m b/Courier/CRSessionController.m
new file mode 100644
index 0000000..298f681
--- /dev/null
+++ b/Courier/CRSessionController.m
@@ -0,0 +1,528 @@
+//
+//  CRSessionController.m
+//  Courier
+//
+//  Created by Andrew Smith on 9/22/13.
+//  Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "CRSessionController.h"
+
+#import <UIKit/UIKit.h>
+#import <Reachability/Reachability.h>
+
+#import "CourierLog.h"
+#import "NSURLResponse+Courier.h"
+#import "CRSessionDelegate.h"
+
+#define kCRSessionControllerGenericTaskGroup @"kCRSessionControllerGenericTaskGroup"
+
+@interface CRSessionController ()
+
+/**
+ *  The keys are the group names, and the values are arrays of associated tasks
+ */
+@property (nonatomic, strong) NSMutableDictionary *groups;
+
+/**
+ *  The keys are a unique string, the values are a dictionary with the task object
+ *  and associated group.
+ */
+@property (nonatomic, strong) NSMutableDictionary *tasksByToken;
+
+/**
+ *  The keys are the task IDs, and the values are the associated task object
+ */
+@property (nonatomic, strong) NSMutableDictionary *tasksByIdentifier;
+
+/**
+ 
+ */
+@property (nonatomic, strong) NSMutableDictionary *sessionDelegates;
+
+/**
+ Internal reachability object
+ */
+@property (nonatomic, strong) Reachability *reachabilityObject;
+
+/**
+ This is a serial queue used to manage NSURLSessionTasks. This queue is used to ensure that you
+ can interact with the CRSessionController in a thread safe way.
+ */
+@property (nonatomic, strong) NSOperationQueue *serialQueue;
+
+@end
+
+@implementation CRSessionController
+
+- (void)dealloc
+{
+    [self.session invalidateAndCancel];
+}
+
+- (instancetype)init
+{
+    return [self initWithSessionConfiguration:nil];
+}
+
+- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration
+{
+    self = [super init];
+    if (self != nil) {
+        //
+        // Use default session configuration if none provided
+        //
+        if (configuration == nil) {
+            configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
+        }
+    
+        //
+        // NSURLSession delegate queue
+        //
+        NSOperationQueue *delegateQueue = [NSOperationQueue new];
+        delegateQueue.maxConcurrentOperationCount = 1;
+        _sessionDelegateOperationQueue = delegateQueue;
+        
+        //
+        // Build NSURLSession
+        //
+        CRSessionDelegate *sessionDelegate =
+            [CRSessionDelegate sessionDelegateWithSessionController:self];
+        _session = [NSURLSession sessionWithConfiguration:configuration
+                                                 delegate:sessionDelegate
+                                            delegateQueue:_sessionDelegateOperationQueue];
+        
+        //
+        // TODO
+        //
+        NSOperationQueue *serialQueue = [NSOperationQueue new];
+        serialQueue.maxConcurrentOperationCount = 1;
+        _serialQueue = serialQueue;
+
+        //
+        // Keep track of tasks
+        //
+        _groups            = [NSMutableDictionary dictionary];
+        _tasksByToken      = [NSMutableDictionary dictionary];
+        _tasksByIdentifier = [NSMutableDictionary dictionary];
+        _sessionDelegates  = [NSMutableDictionary dictionary];
+        
+        //
+        // Reachable
+        //
+        _reachabilityObject = [Reachability reachabilityForInternetConnection];
+    }
+    return self;
+}
+
++ (instancetype)sessionControllerWithConfiguration:(NSURLSessionConfiguration *)configuration
+{
+    return [[self alloc] initWithSessionConfiguration:configuration];
+}
+
+#pragma mark - Task creation
+
+- (NSURLSessionDataTask *)dataTaskForRequest:(NSURLRequest *)request
+                           completionHandler:(void (^)(NSData *data,
+                                                       NSURLResponse *response,
+                                                       BOOL cachedResponse,
+                                                       NSError *error))completionHandler
+{
+    NSURLSessionDataTask *task = [self dataTaskForRequest:request
+                                                taskGroup:nil
+                                        completionHandler:completionHandler];
+    return task;
+}
+
+- (NSURLSessionDataTask *)dataTaskForRequest:(NSURLRequest *)request
+                                   taskGroup:(NSString *)group
+                           completionHandler:(void (^)(NSData *data,
+                                                       NSURLResponse *response,
+                                                       BOOL cachedResponse,
+                                                       NSError *error))completionHandler
+{
+    CourierLogInfo(@"Creating task for URL : %@", request.URL);
+    
+    //
+    // Unique task token
+    //
+    NSString *token = [self uniqueToken];
+    
+    //
+    // Check for cached response. Pass this into the completion and allow the task
+    // to pass through the normal delegate queue flow.
+    //
+    NSURLCache *urlCache = _session.configuration.URLCache;
+    BOOL cachedResponse = urlCache && [urlCache cachedResponseForRequest:request];
+        
+    //
+    // Create Task
+    //
+    __weak typeof(self) weakSelf = self;
+    NSURLSessionDataTask *task = [_session dataTaskWithRequest:request
+                                             completionHandler:^(NSData *data,
+                                                                 NSURLResponse *response,
+                                                                 NSError *error) {
+                                                 __strong typeof(self) strongSelf = weakSelf;
+                                                 [strongSelf logResponse:response data:data error:error];
+                                                 [strongSelf removeTaskWithToken:token];
+                                                 if (completionHandler) completionHandler(data,
+                                                                                          response,
+                                                                                          cachedResponse,
+                                                                                          error);
+                                             }];
+    
+    //
+    // Keep track of the task
+    //
+    [self addTask:task
+        withToken:token
+          toGroup:group];
+    
+    return task;
+}
+
+- (void)logResponse:(NSURLResponse *)response
+               data:(NSData *)data
+              error:(NSError *)error
+{
+    CourierLogInfo(@"Finishing task for URL : %@ status code: %li",
+                   response.URL,
+                   (long)response.cou_statusCode);
+
+#if DEBUG && COURIER_LOG
+    NSMutableString *logString = [NSMutableString string];
+    
+    [logString appendString:@"\n########################################"];
+    [logString appendFormat:@"\nResponse: %@", response];
+    
+    NSString *encodingName = response.textEncodingName;
+    NSString *dataString = nil;
+    if (encodingName) {
+        NSStringEncoding encodingType = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)encodingName));
+        dataString =  [[NSString alloc] initWithData:data encoding:encodingType];
+    }
+    
+    [logString appendFormat:@"\nData: %@", dataString];
+    [logString appendFormat:@"\nError: %@", error];
+    [logString appendString:@"\n########################################"];
+    
+    NSLog(@"%@", logString);
+#endif
+}
+
+- (NSURLSessionConfiguration *)configuration
+{
+    return _session.configuration;
+}
+
+#pragma mark - Add/Remove Tasks
+
+- (NSString *)uniqueToken
+{
+    return [[NSProcessInfo processInfo] globallyUniqueString];
+}
+
+- (void)addTask:(NSURLSessionTask *)task
+      withToken:(NSString *)token
+        toGroup:(NSString *)group
+{
+    typeof(self) __weak weakSelf = self;
+    [self.serialQueue addOperationWithBlock:^{
+        typeof(self) __strong strongSelf = weakSelf;
+
+        if (!task) return;
+        NSString *internalGroup = group;
+        if (!internalGroup || internalGroup.length == 0) {
+            internalGroup = kCRSessionControllerGenericTaskGroup;
+        }
+        
+        CourierLogInfo(@"Adding task to group : %@", group);
+        
+        //
+        // Add task to Groups
+        //
+        NSMutableArray *tasks = [strongSelf.groups objectForKey:internalGroup];
+        if (!tasks) {
+            tasks = [NSMutableArray array];
+        }
+        [tasks addObject:task];
+        [strongSelf.groups setObject:tasks
+                    forKey:internalGroup];
+        
+        //
+        // Add task to Tasks by token
+        //
+        NSMutableDictionary *taskDict = [strongSelf.tasksByToken objectForKey:internalGroup];
+        if (!taskDict) {
+            taskDict = [NSMutableDictionary dictionary];
+        }
+        taskDict[@"task"] = task;
+        taskDict[@"group"] = internalGroup;
+        [strongSelf.tasksByToken setObject:taskDict
+                                    forKey:token];
+        
+        //
+        // Add task to tasks by id
+        //
+        [strongSelf.tasksByIdentifier setObject:task
+                                         forKey:@(task.taskIdentifier)];
+    }];
+}
+
+- (void)removeTaskWithToken:(NSString *)token
+{
+    typeof(self) __weak weakSelf = self;
+    [self.serialQueue addOperationWithBlock:^{
+        typeof(self) __strong strongSelf = weakSelf;
+        NSDictionary     *taskDict = [strongSelf.tasksByToken objectForKey:token];
+        NSString         *group    = taskDict[@"group"];
+        NSURLSessionTask *task     = taskDict[@"task"];
+        
+        if (!task) return;
+        if (!group || group.length == 0) {
+            group = kCRSessionControllerGenericTaskGroup;
+        }
+        
+        //
+        // Remove task delegate
+        //
+        id delegate = [strongSelf NSURLSessionTaskDelegateForTask:task];
+        [strongSelf removeNSURLSessionTaskDelegate:delegate];
+        
+        CourierLogInfo(@"Removing task for URL: %@ in group: %@",
+                        task.currentRequest.URL,
+                        group);
+            
+        //
+        // Get the tasks
+        //
+        NSMutableArray *groupTasks = [strongSelf.groups objectForKey:group];
+        
+        //
+        // Remove the task
+        //
+        [strongSelf.tasksByToken removeObjectForKey:token];
+        [strongSelf.tasksByIdentifier removeObjectForKey:@(task.taskIdentifier)];
+        [groupTasks removeObject:task];
+        
+        //
+        // If empty, remove tasks array
+        //
+        if (groupTasks.count == 0) {
+            [strongSelf.groups removeObjectForKey:group];
+        }
+    }];
+}
+
+- (void)addNSURLSessionTaskDelegate:(id <NSURLSessionTaskDelegate>)delegate
+                            forTask:(NSURLSessionTask *)task
+{
+	NSParameterAssert(delegate);
+	NSParameterAssert(task);
+	if (delegate == nil || task == nil) return;
+	@synchronized(_sessionDelegates)	{
+		[_sessionDelegates setObject:delegate
+		                      forKey:[NSValue valueWithNonretainedObject:task]];
+	}
+}
+
+- (void)removeNSURLSessionTaskDelegate:(id <NSURLSessionTaskDelegate>)delegate
+{
+    if (delegate == nil) return;
+    @synchronized(_sessionDelegates) {
+        __block id taskKey;
+        [_sessionDelegates enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
+            if ([obj isEqual:delegate]) {
+                taskKey = key;
+                *stop = YES;
+            }
+        }];
+        [_sessionDelegates removeObjectForKey:taskKey];
+    }
+}
+
+- (id <NSURLSessionTaskDelegate>)NSURLSessionTaskDelegateForTask:(NSURLSessionTask *)task
+{
+    return [_sessionDelegates objectForKey:[NSValue valueWithNonretainedObject:task]];
+}
+
+@end
+
+@implementation CRSessionController (TaskManagement)
+
+- (NSURLSessionTask *)taskWithIdentifier:(NSUInteger)taskIdentifier
+{
+    return [_tasksByIdentifier objectForKey:@(taskIdentifier)];
+}
+
+- (BOOL)hasTaskWithIdentifier:(NSUInteger)taskIdentifier
+{
+    return [_tasksByIdentifier objectForKey:@(taskIdentifier)] != nil;
+}
+
+- (BOOL)hasTasksInGroup:(NSString *)group
+              withState:(NSURLSessionTaskState)state
+{
+    __block BOOL result = NO;
+    typeof(self) __weak weakSelf = self;
+    NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{
+        NSArray *groupTasks = weakSelf.groups[group];
+        for (NSURLSessionTask *task in groupTasks) {
+            if (task.state == state) {
+                result = YES;
+                return;
+            }
+        }
+    }];
+    
+    [self.serialQueue addOperations:@[op]
+                  waitUntilFinished:YES];
+
+    return result;
+}
+
+- (void)suspendTasksInGroup:(NSString *)group
+{
+    typeof(self) __weak weakSelf = self;
+    [self.serialQueue addOperationWithBlock:^{
+        typeof(self) __strong strongSelf = weakSelf;
+        CourierLogInfo(@"Suspend tasks in group : %@", group);
+        NSArray *tasks = [strongSelf.groups valueForKey:group];
+        for (NSURLSessionTask *task in tasks) {
+            [task suspend];
+        }
+    }];
+}
+
+- (void)resumeTasksInGroup:(NSString *)group
+{
+    typeof(self) __weak weakSelf = self;
+    [self.serialQueue addOperationWithBlock:^{
+        typeof(self) __strong strongSelf = weakSelf;
+        CourierLogInfo(@"Resume tasks in group : %@", group);
+        NSArray *tasks = [strongSelf.groups valueForKey:group];
+        for (NSURLSessionTask *task in tasks) {
+            [task resume];
+        }
+    }];
+}
+
+- (void)cancelTasksInGroup:(NSString *)group
+{
+    typeof(self) __weak weakSelf = self;
+    [self.serialQueue addOperationWithBlock:^{
+        typeof(self) __strong strongSelf = weakSelf;
+        CourierLogInfo(@"Canceling tasks in group : %@", group);
+        NSArray *tasks = [strongSelf.groups valueForKey:group];
+        for (NSURLSessionTask *task in tasks) {
+            [task cancel];
+        }
+    }];
+}
+
+- (void)suspendAllTasks
+{
+    typeof(self) __weak weakSelf = self;
+    [self.serialQueue addOperationWithBlock:^{
+        typeof(self) __strong strongSelf = weakSelf;
+        CourierLogInfo(@"Suspend all tasks");
+        for (NSString *groupName in strongSelf.groups.allKeys) {
+            [strongSelf suspendTasksInGroup:groupName];
+        }
+    }];
+}
+
+- (void)resumeAllTasks
+{
+    typeof(self) __weak weakSelf = self;
+    [self.serialQueue addOperationWithBlock:^{
+        typeof(self) __strong strongSelf = weakSelf;
+        CourierLogInfo(@"Resume all tasks");
+        for (NSString *groupName in strongSelf.groups.allKeys) {
+            [strongSelf resumeTasksInGroup:groupName];
+        }
+    }];
+}
+
+- (void)cancelAllTasks
+{
+    typeof(self) __weak weakSelf = self;
+    [self.serialQueue addOperationWithBlock:^{
+        typeof(self) __strong strongSelf = weakSelf;
+        CourierLogInfo(@"Cancel all tasks");
+        for (NSString *groupName in strongSelf.groups.allKeys) {
+            [strongSelf cancelTasksInGroup:groupName];
+        }
+    }];
+}
+
+@end
+
+@implementation CRSessionController (Reachability)
+
+- (BOOL)isInternetReachable
+{
+    return _reachabilityObject.isReachable;
+}
+
+- (void)startReachabilityMonitoring
+{
+    [_reachabilityObject startNotifier];
+}
+
+- (void)stopReachabilityMonitoring
+{
+    [_reachabilityObject stopNotifier];
+}
+
+- (void)setReachabilityStatusChangeBlock:(void (^)(NetworkStatus status))block
+{
+    _reachabilityObject.reachableBlock = ^(Reachability *reachability) {
+        if (block) {
+            block(reachability.currentReachabilityStatus);
+        }
+    };
+    
+    _reachabilityObject.unreachableBlock = ^(Reachability *reachability) {
+        if (block) {
+            block(reachability.currentReachabilityStatus);
+        }
+    };
+}
+
+@end
+
+@implementation CRSessionController (Debug)
+
+- (void)logRequests
+{
+    CourierLogInfo(@"Log current tasks:");
+    NSArray *tasks = _tasksByIdentifier.allValues;
+    for (NSURLSessionTask *task in tasks) {
+        __unused NSString *description = [NSString stringWithFormat:@"task: %@\n URL: %@\nMethod: %@",
+                                          task,
+                                          task.currentRequest.URL,
+                                          task.currentRequest.HTTPMethod];
+        CourierLogInfo(@"%@", description);
+    }
+}
+
+@end
diff --git a/Courier/CRSessionDelegate.h b/Courier/CRSessionDelegate.h
new file mode 100644
index 0000000..184e42b
--- /dev/null
+++ b/Courier/CRSessionDelegate.h
@@ -0,0 +1,12 @@
+#import <Foundation/Foundation.h>
+
+@class CRSessionController;
+
+@interface CRSessionDelegate : NSObject <NSURLSessionDelegate,
+                                         NSURLSessionTaskDelegate>
+
++ (instancetype)sessionDelegateWithSessionController:(CRSessionController *)sessionController;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+@end
diff --git a/Courier/CRSessionDelegate.m b/Courier/CRSessionDelegate.m
new file mode 100644
index 0000000..072b572
--- /dev/null
+++ b/Courier/CRSessionDelegate.m
@@ -0,0 +1,67 @@
+#import "CRSessionDelegate.h"
+
+#import "CRSessionController.h"
+
+@interface CRSessionDelegate ()
+
+@property (nonatomic, weak) CRSessionController *sessionController;
+
+@end
+
+@implementation CRSessionDelegate
+
+- (instancetype)initWithSessionController:(CRSessionController *)sessionController
+{
+    self = [super init];
+    if (self) {
+        _sessionController = sessionController;
+    }
+    return self;
+}
+
++ (instancetype)sessionDelegateWithSessionController:(CRSessionController *)sessionController
+{
+    CRSessionDelegate *delegate = [[self alloc] initWithSessionController:sessionController];
+    return delegate;
+}
+
+#pragma mark - NSURLSessionTaskDelegate
+
+- (void)URLSession:(NSURLSession *)session
+              task:(NSURLSessionTask *)task
+willPerformHTTPRedirection:(NSHTTPURLResponse *)response
+        newRequest:(NSURLRequest *)request
+ completionHandler:(void (^)(NSURLRequest *))completionHandler
+{
+    id delegate = [self.sessionController NSURLSessionTaskDelegateForTask:task];
+    if ([delegate respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
+        [delegate URLSession:session
+                        task:task
+  willPerformHTTPRedirection:response
+                  newRequest:request
+           completionHandler:completionHandler];
+    } else {
+        if (completionHandler) completionHandler(request);
+    }
+}
+
+- (void)URLSession:(NSURLSession *)session
+              task:(NSURLSessionTask *)task
+didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
+ completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition,
+                             NSURLCredential *))completionHandler
+{
+    id delegate = [self.sessionController NSURLSessionTaskDelegateForTask:task];
+    if ([delegate respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {
+        [delegate URLSession:session
+                        task:task
+         didReceiveChallenge:challenge
+           completionHandler:completionHandler];
+    } else {
+        if (completionHandler) {
+            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
+        }
+    }
+}
+
+@end
diff --git a/Courier/Courier.h b/Courier/Courier.h
new file mode 100644
index 0000000..e7e4cef
--- /dev/null
+++ b/Courier/Courier.h
@@ -0,0 +1,37 @@
+//
+//  CourierSession.h
+//  Courier
+//
+//  Created by Andrew Smith on 9/22/13.
+//  Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+//
+
+#ifndef Courier_CourierSession_h
+#define Courier_CourierSession_h
+
+#import "NSString+Courier.h"
+#import "NSMutableURLRequest+Courier.h"
+#import "NSURLResponse+Courier.h"
+#import "NSDictionary+Courier.h"
+#import "CRSessionController.h"
+
+#endif
+
diff --git a/Courier/CourierLog.h b/Courier/CourierLog.h
new file mode 100644
index 0000000..8d5a63a
--- /dev/null
+++ b/Courier/CourierLog.h
@@ -0,0 +1,22 @@
+//
+//  CourierLog.h
+//  Courier
+//
+//  Created by Andrew Smith on 2/3/14.
+//  Copyright (c) 2014 Andrew B. Smith. All rights reserved.
+//
+
+#ifndef Courier_CourierLog_h
+#define Courier_CourierLog_h
+
+#define COURIER_LOG 0
+
+#if DEBUG && COURIER_LOG
+    #define CourierLogInfo(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__])
+    #define CourierLogWarning(...) NSLog(@"\n!!!!\n%s %@\n!!!!\n", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__])
+#else
+    #define CourierLogInfo(...) do { } while (0)
+    #define CourierLogWarning(...) do { } while (0)
+#endif
+
+#endif
diff --git a/Courier/NSDictionary+Courier.h b/Courier/NSDictionary+Courier.h
new file mode 100644
index 0000000..e0a06de
--- /dev/null
+++ b/Courier/NSDictionary+Courier.h
@@ -0,0 +1,50 @@
+//
+//  NSDictionary+Courier.h
+//  Courier
+//
+//  Created by Andrew Smith on 2/28/13.
+//  Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSDictionary (Courier)
+
+/**
+ * NSUTF8StringEncoding encoded JSON string
+ */
+- (NSString *)cou_asJSONString;
+
+/**
+ * NSJSONSerialization representation
+ */
+- (NSData *)cou_asJSONData;
+
+/**
+ * & joined form URL encoded string as NSData
+ */
+- (NSData *)cou_asFormURLEncodedData;
+
+/**
+ * Returns URL formated query string, "key1=value1&key2=value2..."
+ */
+- (NSString *)cou_asFormURLEncodedString;
+
+@end
diff --git a/Courier/NSDictionary+Courier.m b/Courier/NSDictionary+Courier.m
new file mode 100644
index 0000000..a4bc096
--- /dev/null
+++ b/Courier/NSDictionary+Courier.m
@@ -0,0 +1,76 @@
+//
+//  NSDictionary+Courier.m
+//  Courier
+//
+//  Created by Andrew Smith on 2/28/13.
+//  Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "NSDictionary+Courier.h"
+#import "NSString+Courier.h"
+
+@implementation NSDictionary (Courier)
+
+#pragma mark - application/json
+
+- (NSString *)cou_asJSONString
+{
+    NSData *jsonData = [self cou_asJSONData];
+    if (!jsonData) return nil;
+    return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];    
+}
+
+- (NSData *)cou_asJSONData
+{
+    NSError *error;
+    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self
+                                                       options:0
+                                                         error:&error];
+    
+    if (!jsonData) NSLog(@"JSON serialization error: %@", error);
+    
+    return jsonData;
+}
+
+#pragma mark - application/x-www-form-urlencoded
+
+- (NSString *)cou_asFormURLEncodedString
+{
+    __block NSMutableArray *mutableParameterComponents = [NSMutableArray array];
+    
+    [self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
+        NSString *encodedKey   = [[key description] cou_urlEncodedStringWithEncoding:NSUTF8StringEncoding];
+        NSString *encodedValue = [[obj description] cou_urlEncodedStringWithEncoding:NSUTF8StringEncoding];
+        NSString *component    = [NSString stringWithFormat:@"%@=%@", encodedKey, encodedValue];
+        [mutableParameterComponents addObject:component];
+    }];
+    
+    NSString *andJoinedString = [mutableParameterComponents componentsJoinedByString:@"&"];
+
+    return andJoinedString;
+}
+
+- (NSData *)cou_asFormURLEncodedData
+{
+    NSString *andJoinedString = [self cou_asFormURLEncodedString];
+    return [andJoinedString dataUsingEncoding:NSUTF8StringEncoding];
+}
+
+@end
diff --git a/Courier/NSMutableURLRequest+Courier.h b/Courier/NSMutableURLRequest+Courier.h
new file mode 100644
index 0000000..cd32168
--- /dev/null
+++ b/Courier/NSMutableURLRequest+Courier.h
@@ -0,0 +1,53 @@
+//
+//  NSMutableURLRequest+Courier.h
+//  Courier
+//
+//  Created by Andrew Smith on 7/25/13.
+//  Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef enum {
+    CRURLRequestEncodingUnknown,
+    CRURLFormURLParameterEncoding,
+    CRURLJSONParameterEncoding,
+    CRURLImageEncoding,
+} CRURLRequestEncoding;
+
+@interface NSMutableURLRequest (Courier)
+
++ (NSMutableURLRequest *)cou_requestWithMethod:(NSString *)method
+                                          path:(NSString *)path;
+
+/**
+ 
+ @note This will set the Content-Type header to the appropriate type, unless you
+ specify your own.
+ 
+ */
++ (NSMutableURLRequest *)cou_requestWithMethod:(NSString *)method
+                                          path:(NSString *)path
+                                      encoding:(CRURLRequestEncoding)encoding
+                                 URLParameters:(NSDictionary *)urlParameters
+                            HTTPBodyParameters:(NSDictionary *)httpBodyParameters
+                                        header:(NSDictionary *)header;
+
+@end
diff --git a/Courier/NSMutableURLRequest+Courier.m b/Courier/NSMutableURLRequest+Courier.m
new file mode 100644
index 0000000..9648716
--- /dev/null
+++ b/Courier/NSMutableURLRequest+Courier.m
@@ -0,0 +1,132 @@
+//
+//  NSMutableURLRequest+Courier.m
+//  Courier
+//
+//  Created by Andrew Smith on 7/25/13.
+//  Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "NSMutableURLRequest+Courier.h"
+
+#import "NSDictionary+Courier.h"
+
+@implementation NSMutableURLRequest (Courier)
+
++ (NSMutableURLRequest *)cou_requestWithMethod:(NSString *)method
+                                      path:(NSString *)path
+{
+    return [self cou_requestWithMethod:method
+                              path:path
+                          encoding:CRURLRequestEncodingUnknown
+                     URLParameters:nil
+                HTTPBodyParameters:nil
+                            header:nil];
+}
+
++ (NSMutableURLRequest *)cou_requestWithMethod:(NSString *)method
+                                          path:(NSString *)path
+                                      encoding:(CRURLRequestEncoding)encoding
+                                 URLParameters:(NSDictionary *)urlParameters
+                            HTTPBodyParameters:(NSDictionary *)httpBodyParameters
+                                        header:(NSDictionary *)header
+{
+    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
+    
+    [request setURLPath:path withParameters:urlParameters];
+    [request setHTTPMethod:method];
+    [request setHTTPBodyDataWithParameters:httpBodyParameters encoding:encoding];
+    [request setAllHTTPHeaderFields:header];
+    
+    // Only add content type if there are body params. Play! filters seems to
+    // have an issue with setting Content-Type header but not provide a body.
+    if (httpBodyParameters && httpBodyParameters.count > 0) {
+        [request addHeaderContentTypeForEncoding:encoding];
+    }
+    
+    return request;
+}
+
+#pragma mark - URL
+
+- (void)setURLPath:(NSString *)path
+    withParameters:(NSDictionary *)parameters
+{
+    //
+    // Append path with URL params, if present
+    //
+    if (parameters.count > 0) {
+        NSString *stringToAppend = [path rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@";
+        NSString *newPath = [path stringByAppendingFormat:stringToAppend, [parameters cou_asFormURLEncodedString]];
+        path = newPath;
+    }
+    
+    self.URL = [NSURL URLWithString:path];
+}
+
+#pragma mark - Header
+
+- (void)addHeaderContentTypeForEncoding:(CRURLRequestEncoding)encoding
+{
+    //
+    // Bail if content type is already set
+    //
+    if (self.allHTTPHeaderFields[@"Content-Type"]) return;
+
+    //
+    // Set the content type
+    //
+    if (encoding == CRURLFormURLParameterEncoding) {
+        // Form URL
+        NSString *charset = (__bridge NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
+        NSString *type = [NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@", charset];
+        [self addValue:type forHTTPHeaderField:@"Content-Type"];
+    } else if (encoding == CRURLJSONParameterEncoding) {
+        // JSON
+        [self addValue:@"application/json,text/json,text/javascript" forHTTPHeaderField:@"Content-Type"];
+    } else if (encoding == CRURLImageEncoding) {
+        [self addValue:@"image/tiff,image/jpeg,image/gif,image/png,image/ico,image/x-icon,image/bmp,image/x-bmp,image/x-xbitmap,image/x-win-bitmap"
+    forHTTPHeaderField:@"Content-Type"];
+    }
+}
+
+#pragma mark - Body
+
+- (void)setHTTPBodyDataWithParameters:(NSDictionary *)parameters
+                             encoding:(CRURLRequestEncoding)encoding
+{
+    if (!parameters) return;
+    
+    NSData *bodyData = nil;
+    
+    if (encoding == CRURLFormURLParameterEncoding) {
+        // Form URL
+        bodyData = [parameters cou_asFormURLEncodedData];
+    } else if (encoding == CRURLJSONParameterEncoding) {
+        // JSON
+        bodyData = [parameters cou_asJSONData];
+    } else {
+        // Unknown encoding. Pass it through.
+        bodyData = [NSKeyedArchiver archivedDataWithRootObject:parameters];
+    }
+    
+    [self setHTTPBody:bodyData];
+}
+
+@end
diff --git a/Courier/NSString+Courier.h b/Courier/NSString+Courier.h
new file mode 100644
index 0000000..f6eca57
--- /dev/null
+++ b/Courier/NSString+Courier.h
@@ -0,0 +1,35 @@
+//
+//  NSString+Courier.h
+//  Courier
+//
+//  Created by Andrew Smith on 10/19/11.
+//  Copyright (c) 2011 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy 
+// of this software and associated documentation files (the "Software"), to deal 
+// in the Software without restriction, including without limitation the rights 
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+// of the Software, and to permit persons to whom the Software is furnished to do so, 
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included 
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSString (Courier)
+
+- (NSString *)cou_urlEncodedString;
+
+- (NSString *)cou_urlEncodedStringWithEncoding:(NSStringEncoding)encoding;
+
+@end
diff --git a/Courier/NSString+Courier.m b/Courier/NSString+Courier.m
new file mode 100644
index 0000000..cfb3a0e
--- /dev/null
+++ b/Courier/NSString+Courier.m
@@ -0,0 +1,47 @@
+//
+//  NSString+Courier.m
+//  Courier
+//
+//  Created by Andrew Smith on 10/19/11.
+//  Copyright (c) 2011 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#import "NSString+Courier.h"
+
+@implementation NSString (Courier)
+
+- (NSString *)cou_urlEncodedString
+{
+	return [self cou_urlEncodedStringWithEncoding:NSUTF8StringEncoding];
+}
+
+// See http://github.com/pokeb/asi-http-request/raw/master/Classes/ASIFormDataRequest.m
+- (NSString *)cou_urlEncodedStringWithEncoding:(NSStringEncoding)encoding
+{
+	NSString *urlEncodedString = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
+                                                                                                       (__bridge CFStringRef)self,
+                                                                                                       NULL, (CFStringRef)@":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`",
+                                                                                                       CFStringConvertNSStringEncodingToEncoding(encoding));
+    return urlEncodedString ? urlEncodedString : @"";
+}
+
+@end
diff --git a/Courier/NSURLResponse+Courier.h b/Courier/NSURLResponse+Courier.h
new file mode 100644
index 0000000..a0dd9e4
--- /dev/null
+++ b/Courier/NSURLResponse+Courier.h
@@ -0,0 +1,41 @@
+//
+//  NSURLResponse+Courier.h
+//  Courier
+//
+//  Created by Andrew Smith on 8/31/13.
+//  Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSURLResponse (Courier)
+
+/**
+ @returns The HTTP status code for the response.
+ */
+@property (readonly) NSInteger cou_statusCode;
+
+/**
+ @returns YES if the response was successful, checking the HTTP status code for 2xx.
+ */
+- (BOOL)cou_success;
+
+@end
diff --git a/Courier/NSURLResponse+Courier.m b/Courier/NSURLResponse+Courier.m
new file mode 100644
index 0000000..6162da1
--- /dev/null
+++ b/Courier/NSURLResponse+Courier.m
@@ -0,0 +1,41 @@
+//
+//  NSURLResponse+Courier.m
+//  Courier
+//
+//  Created by Andrew Smith on 8/31/13.
+//  Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "NSURLResponse+Courier.h"
+
+@implementation NSURLResponse (Courier)
+
+- (NSInteger)cou_statusCode
+{
+    return [(NSHTTPURLResponse *)self statusCode];
+}
+
+- (BOOL)cou_success
+{
+    NSInteger statusCode = [(NSHTTPURLResponse *)self statusCode];
+    return (statusCode >= 200 && statusCode < 300);
+}
+
+@end
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6aaf5e1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Andrew B. Smith (http://github.com/Drewsmits)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..87c1302
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+# Courier
+
+Courier is a simple network layer built on NSURLSession and various categories of convenience.
+
+* Easy network activity display management
+* Respond to changes in reachability
+* Respond to 401 unauthorized errors
+* Request logging
+* Easily build NSURLRequests with proper encoding
+
+
+## Getting Started
+
+```objc
+#import <Courier/Courier.h>
+
+// Default config
+NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
+
+// Add additional headers
+config.HTTPAdditionalHeaders = @{@"Accept" : @"application/json"};
+
+// Be a good API citizen and limit to 1 HTTP connection per host
+config.HTTPMaximumConnectionsPerHost = 1;
+
+// Build session controller
+CRSessionController *sessionController = [CRSessionController sessionControllerWithConfiguration:config 
+                                                                                        delegate:controller];
+
+// Build a POST request
+NSMutableURLRequest *request = [NSMutableURLRequest requestWithMethod:@"POST"
+                                                                 path:@"https://service.com/api"
+                                                             encoding:CR_URLJSONParameterEncoding
+                                                        URLParameters:@{@"urlParam" : @"value"}
+                                                   HTTPBodyParameters:@{@"bodyParam" : @"value"}
+                                                               header:@{@"Header-Name" : @"value"}];
+
+
+// Build a task
+NSURLSessionDataTask *task = [sessionController dataTaskForRequest:request
+                                                 completionHandler:^(NSData *data,
+                                                                     NSURLResponse *response,
+                                                                     NSError *error) {
+                                                     if (response.success) {
+                                                         // Hurrah!
+                                                     }
+                                                 }];
+
+// Start task
+[task resume];
+
+```
+
+## Externals
+
+* [Reachability library](https://github.com/tonymillion/Reachability) was created and maintained by [Tony Million](https://github.com/tonymillion).