AFNetworking 2.xでプロキシを使う

# はじめに
## 概要
* はじめに、`NSURLSession`でのプロキシ通信の実装例を示します
* つぎに、`AFNetworking 2.x`でのプロキシ通信の実装例を示します
* 最後に、補足としてPACファイルからプロキシ設定を取得する実装例を示します
* 実装例は、Mac OS X Yosemite 10.10.3で動作確認しています

## AFNetworkingの補足
* AFNetworking 1.xでは`NSURLConnection`を使っており、`NSURLConnection`はプロキシをサポートしていないため、CFNetwork系のAPIを使って独自に実装する必要があるようです
* 一方、AFNetworking 2.xでは`NSURLSession`を使え、`NSURLSession`は`NSURLSessionConfiguration.connectionProxyDictionary`でプロキシを指定できます
* 参考: [ios - how to set proxy details using AFNetworking - Stack Overflow](http://stackoverflow.com/questions/17629616/how-to-set-proxy-details-using-afnetworking/17645314#17645314)

## こちらも参照してください
* [Cocoa - NSURLSessionで任意のProxyを使う - Qiita](http://qiita.com/funayoi/items/a6ed4004a7763aaef64c)

## 注意
* `AFNetworking 2.x`は、iOS 6以降、Mac OS X 10.8以降をサポートしています
 * [AFNetworking - Requirements](https://github.com/AFNetworking/AFNetworking#requirements)

# (NSURLSession) プロキシ通信の実装例
* `NSURLSession``NSURLSessionDataTask`を直接使用する場合の実装例です
* 実行環境: OS X Yosemite 10.10.3
* Xcode 6.3.2

```objc

- (IBAction)proxyConnectAction:(id)sender {  
//  NSDictionary *httpProxy = @{(id)kCFNetworkProxiesHTTPEnable:  @(1),
//                            (id)kCFStreamPropertyHTTPProxyHost: host,
//                            (id)kCFStreamPropertyHTTPProxyPort: port};
  
  // 注意: iOSでは`HTTPS`のkeyが定義されていません
  NSDictionary *httpsProxy = @{(id)kCFNetworkProxiesHTTPSEnable:    @(1),
                               (id)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
                               (id)kCFStreamPropertyHTTPSProxyPort: @(8888)};

  NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
  configuration.connectionProxyDictionary = httpsProxy;
  NSURL *url = [NSURL URLWithString:@"https://www.example.com/foobar.png"];
  NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
  NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    NSLog(@"response: %@", response);
    NSLog(@"data length: %ld", data.length);
    if (error) {
      NSLog(@"error:%@", error);
    }
  }];
  [dataTask resume];
}
```

# (AFNetworking 2.x) プロキシ通信の実装例
* AFNetworkingの`AFHTTPSessionManager`を使用する場合の実装例です
* 実行環境: OS X Yosemite 10.10.3
* Xcode 6.3.2
* AFNetworking 2.5.4

## RestClientの例

```objc:RestClient.h
#import <Foundation/Foundation.h>

@interface RestClient : NSObject
@property (copy, nonatomic) NSURL *baseURL;
@property (copy, nonatomic) NSDictionary *proxyDictionary;
@property (nonatomic) BOOL allowInvalidCertificates; // For debugging (Default: NO)

- (instancetype)initWithBaseURL:(NSURL *)baseURL;
- (instancetype)initWithBaseURL:(NSURL *)baseURL proxyDictionary:(NSDictionary *)proxyDictionary;

- (void)getWithPath:(NSString *)path
         parameters:(NSDictionary *)parameters
            success:(void (^)(id responseObject))success
            failure:(void (^)(NSError *error))failure;

@end
```

* [initWithBaseURL:sessionConfiguration:](http://cocoadocs.org/docsets/AFNetworking/2.0.0/Classes/AFHTTPSessionManager.html#//api/name/initWithBaseURL:sessionConfiguration:)を使って、`AFHTTPSessionManager`を作成します
* `NSURLSessionConfiguration.connectionProxyDictionary`に、プロキシの情報を入れたNSDictionaryを指定します


```objc:RestClient.m
#import "RestClient.h"
#import <AFNetworking/AFNetworking.h>

@interface RestClient ()
@property (strong) AFHTTPSessionManager *httpSessionManager;

@end

@implementation RestClient

- (instancetype)initWithBaseURL:(NSURL *)baseURL {
  self = [super init];
  if (!self) {
    return nil;
  }
  
  self.baseURL = baseURL;
  
  return self;
}

- (instancetype)initWithBaseURL:(NSURL *)baseURL proxyDictionary:(NSDictionary *)proxyDictionary {
  self = [self initWithBaseURL:baseURL];
  if (!self) {
    return nil;
  }
  
  self.proxyDictionary = proxyDictionary;
  
  return self;
}

- (AFHTTPSessionManager *)_sessionManager {
  AFHTTPSessionManager *manager = self.httpSessionManager;
  
  if (!manager) {
    // Configure AFHTTPSessionManager
    NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
    sessionConfig.connectionProxyDictionary = self.proxyDictionary;
    
    manager = [[AFHTTPSessionManager alloc] initWithBaseURL:self.baseURL sessionConfiguration:sessionConfig];
    manager.securityPolicy.allowInvalidCertificates = self.allowInvalidCertificates;
    
    // Set Serializer
    manager.requestSerializer  = [AFJSONRequestSerializer serializer];
    manager.responseSerializer = [AFJSONResponseSerializer serializer];
    
    // Configure JSON Request Serializer
    manager.requestSerializer.timeoutInterval = 20.0f;
    manager.requestSerializer.cachePolicy     = NSURLRequestReloadIgnoringCacheData;
    
    // Set default header to requestSerializer
    [manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Accept"];
    [manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"content-type"];
    
    // Configure JSON Response Serializer
    ((AFJSONResponseSerializer *)(manager.responseSerializer)).readingOptions |= NSJSONReadingAllowFragments;
    self.httpSessionManager = manager;
  }
  
  return manager;
}

- (void)getWithPath:(NSString *)path
         parameters:(NSDictionary *)parameters
            success:(void (^)(id responseObject))success
            failure:(void (^)(NSError *error))failure {
  AFHTTPSessionManager *manager = [self _sessionManager];
  manager.responseSerializer = [AFHTTPResponseSerializer serializer];
  [manager GET:path parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
    NSLog(@"task:%@", task);
    success(responseObject);
  } failure:^(NSURLSessionDataTask *task, NSError *error) {
    NSLog(@"task:%@", task);
    failure(error);
  }];
}

@end
```

## RestClientを使う側

```objc:Example.m

// [proxy] https://127.0.0.1:8888
NSDictionary *httpsProxy = @{(id)kCFNetworkProxiesHTTPSEnable:    @(1),
                             (id)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
                             (id)kCFStreamPropertyHTTPSProxyPort: @(8888)};

NSURL *baseURL = [self _baseURL];
NSString *path = [self _path];

RestClient *client = [[RestClient alloc] initWithBaseURL:baseURL proxyDictionary:httpsProxy];
[client getWithPath:path parameters:nil success:^(id responseObject) {
  NSData *data = (NSData *)responseObject;
  NSImage *image = [[NSImage alloc] initWithData:data];
  self.imageView.image = image;
} failure:^(NSError *error) {
  NSLog(@"error: %@", error);
  self.imageView.image = nil;
}];
```

# (補足) PACファイルからプロキシ設定を取得する
## システム環境設定
* `システム環境設定`＞`ネットワーク`＞`任意のアダプタ`＞`プロキシ`でPACファイルを指定した場合

![Proxy Settings](https://qiita-image-store.s3.amazonaws.com/881/49300/e9c9961f-569a-ceac-bb99-68c186700b81.png)

## 実装例
* 実行環境: OS X Yosemite 10.10.3
* Xcode 6.3.2
* CoreFoundationの`CFNetworkCopyProxiesForAutoConfigurationScript()`使うと、これから接続するURLに該当するプロキシの情報を取得できます

```objc

- (void)_proxiesWithPACScriptURL:(NSURL *)pacScriptURL intendedURL:(NSURL *)intendedURL {
  if (!pacScriptURL || !intendedURL) {
    return;
  }
  
  NSError *error = nil;
  NSString *script;
  
  script = [NSString stringWithContentsOfURL:pacScriptURL usedEncoding:NULL error:&error];
  if (error) {
    NSLog(@"error: %@", error);
    return;
  }
  
  CFErrorRef cfErrorRef = NULL;
  NSArray *proxies = (__bridge NSArray *)CFNetworkCopyProxiesForAutoConfigurationScript((__bridge CFStringRef)script, (__bridge CFURLRef)intendedURL, &cfErrorRef);
  if (cfErrorRef || !proxies.count) {
    NSError *error = (__bridge NSError *)cfErrorRef;
    NSLog(@"error: %@", error);
    return;
  }
  
  NSLog(@"proxies: %@", proxies);
}

- (IBAction)getProxyInfoAction:(id)sender {
  CFDictionaryRef proxies = SCDynamicStoreCopyProxies(NULL);
  NSDictionary *config = (__bridge NSDictionary *)proxies;
  
  NSLog(@"______________________");
  NSLog(@"kSCPropNetProxiesProxyAutoDiscoveryEnable: %@", config[(id)kSCPropNetProxiesProxyAutoDiscoveryEnable]);
  NSLog(@"kSCPropNetProxiesProxyAutoConfigEnable   : %@", config[(id)kSCPropNetProxiesProxyAutoConfigEnable]);
  NSLog(@"kSCPropNetProxiesProxyAutoConfigURLString: %@", config[(id)kSCPropNetProxiesProxyAutoConfigURLString]);
  
  NSLog(@"kSCPropNetProxiesHTTPEnable  : %@", config[(id)kSCPropNetProxiesHTTPEnable]);
  NSLog(@"kSCPropNetProxiesHTTPPort    : %@", config[(id)kSCPropNetProxiesHTTPPort]);
  NSLog(@"kSCPropNetProxiesHTTPSEnable : %@", config[(id)kSCPropNetProxiesHTTPSEnable]);
  NSLog(@"kSCPropNetProxiesHTTPSPort   : %@", config[(id)kSCPropNetProxiesHTTPSPort]);
  NSLog(@"______________________");
  
  NSURL *pacScriptURL = [NSURL URLWithString:config[(id)kSCPropNetProxiesProxyAutoConfigURLString]];
  if (pacScriptURL) {
    NSURL *intendedURL = [NSURL URLWithString:@"https://www.apple.com"];
    [self _proxiesWithPACScriptURL:pacScriptURL intendedURL:intendedURL];
  }
}
```

## PACファイルの例

```js
function FindProxyForURL(url, host) {
  if ( isInNet( host, "127.0.0.1", "255.255.255.255" )) {
    return "DIRECT";
  }
  else if ( dnsDomainIs( host, "beta.example.com") ) {
    return "PROXY 127.0.0.1:8888"
  }
  else {
    return "PROXY 127.0.0.1:8888"
  }
}
```

## 実行結果
```
ProxyInfo[71363:4147289] ______________________
ProxyInfo[71363:4147289] kSCPropNetProxiesProxyAutoDiscoveryEnable: (null)
ProxyInfo[71363:4147289] kSCPropNetProxiesProxyAutoConfigEnable   : 1
ProxyInfo[71363:4147289] kSCPropNetProxiesProxyAutoConfigURLString: http://localhost:4567/proxy4.pac
ProxyInfo[71363:4147289] kSCPropNetProxiesHTTPEnable  : 0
ProxyInfo[71363:4147289] kSCPropNetProxiesHTTPPort    : (null)
ProxyInfo[71363:4147289] kSCPropNetProxiesHTTPSEnable : 0
ProxyInfo[71363:4147289] kSCPropNetProxiesHTTPSPort   : (null)
ProxyInfo[71363:4147289] ______________________
ProxyInfo[71363:4147289] proxies: (
        {
        kCFProxyHostNameKey = "127.0.0.1";
        kCFProxyPortNumberKey = 8888;
        kCFProxyTypeKey = kCFProxyTypeHTTPS;
    }
)
```

# 参照
* [http proxy · Issue #1945 · AFNetworking/AFNetworking](https://github.com/AFNetworking/AFNetworking/issues/1945)
* [NSURLSessionConfiguration Class Reference | connectionProxyDictionary
 Property](https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSessionConfiguration_class/index.html#//apple_ref/occ/instp/NSURLSessionConfiguration/connectionProxyDictionary)
* [ios - How to programmatically add a proxy to an NSURLSession - Stack Overflow](http://stackoverflow.com/questions/28101582/how-to-programmatically-add-a-proxy-to-an-nsurlsession)