このドキュメントでは、iOS クライアントで HTTPDNS を AFNetworking と統合する方法について説明します。
1. 概要
AFNetworking は、iOS 開発で人気のあるネットワークフレームワークです。シンプルな API と強力な機能を提供します。このドキュメントでは、AFNetworking を使用するプロジェクトに HTTPDNS を統合する方法について説明します。
HTTPDNS の基本情報と iOS での使用における技術的な課題については、「ネイティブ iOS シナリオで HTTPDNS を使用する」をご参照ください。
2. 標準 HTTP および非 SNI HTTPS シナリオの統合
このメソッドは、標準の HTTP シナリオおよびサーバ名表示 (SNI) を必要としない HTTPS シナリオに適用されます。
2.1 AFHTTPSessionManager の作成
+ (AFHTTPSessionManager *)sharedAfnManager {
static AFHTTPSessionManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [AFHTTPSessionManager manager];
// セキュリティポリシーを設定します。
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy];
securityPolicy.allowInvalidCertificates = NO;
securityPolicy.validatesDomainName = YES;
manager.securityPolicy = securityPolicy;
// シリアライザーを設定します。
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:
@"application/json", @"text/json", @"text/javascript", @"text/plain", nil];
manager.requestSerializer.timeoutInterval = 10.0f;
});
return manager;
}
2.2 HTTPDNS のドメイン名前解決の実行
+ (NSString *)resolveAvailableIp:(NSString *)host {
HttpDnsService *httpDnsService = [HttpDnsService sharedInstance];
HttpdnsResult *result = [httpDnsService resolveHostSyncNonBlocking:host
byIpType:HttpdnsQueryIPTypeAuto];
if (!result) return nil;
if (result.hasIpv4Address) {
return result.firstIpv4Address;
} else if (result.hasIpv6Address) {
return [NSString stringWithFormat:@"[%@]", result.firstIpv6Address];
}
return nil;
}
2.3 ネットワークリクエストの送信と証明書検証の処理
+ (void)httpDnsQueryWithURL:(NSString *)originalUrl
completionHandler:(void(^)(NSString *message))completionHandler {
NSURL *url = [NSURL URLWithString:originalUrl];
NSString *resolvedIpAddress = [self resolveAvailableIp:url.host];
NSString *requestUrl = originalUrl;
if (resolvedIpAddress) {
// ドメイン名を解決済みの IP アドレスに置き換えます。
requestUrl = [originalUrl stringByReplacingOccurrencesOfString:url.host
withString:resolvedIpAddress];
NSLog(@"HTTPDNS resolution successful. Domain name: %@, IP address: %@", url.host, resolvedIpAddress);
} else {
NSLog(@"HTTPDNS resolution failed. Using original URL: %@", url.host);
}
AFHTTPSessionManager *manager = [self sharedAfnManager];
// 重要: サーバーがドメイン名を正しく識別できるように、Host ヘッダーを設定します。
[manager.requestSerializer setValue:url.host forHTTPHeaderField:@"host"];
// 重要: HTTPS 証明書の検証を設定します。検証には元のドメイン名を使用します。
[manager setSessionDidReceiveAuthenticationChallengeBlock:
^NSURLSessionAuthChallengeDisposition(NSURLSession *session,
NSURLAuthenticationChallenge *challenge,
NSURLCredential **credential) {
if ([challenge.protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust
forDomain:url.host]) {
*credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
return NSURLSessionAuthChallengeUseCredential;
}
}
return NSURLSessionAuthChallengePerformDefaultHandling;
}];
// リクエストを送信します。
[manager GET:requestUrl parameters:nil headers:nil progress:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
NSData *data = [[NSData alloc] initWithData:responseObject];
NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (completionHandler) {
completionHandler([NSString stringWithFormat:@"Request successful: %@", dataStr]);
}
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (completionHandler) {
completionHandler([NSString stringWithFormat:@"Request failed: %@", error.localizedDescription]);
}
}];
}
+ (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain {
// 証明書検証ポリシーを作成します。
NSMutableArray *policies = [NSMutableArray array];
if (domain) {
[policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)];
} else {
[policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()];
}
// 検証ポリシーをサーバー証明書にアタッチします。
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef) policies);
// 証明書の信頼を評価します。
SecTrustResultType result;
SecTrustEvaluate(serverTrust, &result);
return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
}
3. SNI を使用する HTTPS シナリオの統合
このメソッドは、CDN の使用や複数のドメイン名間での IP アドレスの共有など、SNI サポートを必要とするシナリオに適用されます。
3.1 SNI をサポートする AFHTTPSessionManager の設定
+ (AFHTTPSessionManager *)sharedAfnManagerWithSNI {
static AFHTTPSessionManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 重要: SNI を処理するためにカスタムの NSURLProtocol を登録します。
NSMutableArray *protocolsArray = [NSMutableArray arrayWithArray:configuration.protocolClasses];
[protocolsArray insertObject:[HttpDnsNSURLProtocolImpl class] atIndex:0];
[configuration setProtocolClasses:protocolsArray];
manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
// セキュリティポリシーを設定します。
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy];
securityPolicy.allowInvalidCertificates = NO;
securityPolicy.validatesDomainName = YES;
manager.securityPolicy = securityPolicy;
// シリアライザーを設定します。
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:
@"application/json", @"text/json", @"text/javascript", @"text/plain", nil];
manager.requestSerializer.timeoutInterval = 10.0f;
});
return manager;
}
3.2 HTTPDNS のドメイン名前解決の実行
+ (NSString *)resolveAvailableIp:(NSString *)host {
HttpDnsService *httpDnsService = [HttpDnsService sharedInstance];
HttpdnsResult *result = [httpDnsService resolveHostSyncNonBlocking:host
byIpType:HttpdnsQueryIPTypeAuto];
if (!result) return nil;
if (result.hasIpv4Address) {
return result.firstIpv4Address;
} else if (result.hasIpv6Address) {
return [NSString stringWithFormat:@"[%@]", result.firstIpv6Address];
}
return nil;
}
3.3 SNI シナリオでのネットワークリクエストの送信
+ (void)httpDnsQueryWithSNIURL:(NSString *)originalUrl
completionHandler:(void(^)(NSString *message))completionHandler {
NSURL *url = [NSURL URLWithString:originalUrl];
NSString *resolvedIpAddress = [self resolveAvailableIp:url.host];
NSString *requestUrl = originalUrl;
if (resolvedIpAddress) {
requestUrl = [originalUrl stringByReplacingOccurrencesOfString:url.host
withString:resolvedIpAddress];
NSLog(@"HTTPDNS resolution successful. Domain name: %@, IP address: %@", url.host, resolvedIpAddress);
} else {
NSLog(@"HTTPDNS resolution failed. Using original URL: %@", url.host);
}
AFHTTPSessionManager *manager = [self sharedAfnManagerWithSNI];
// Host ヘッダーを設定します。
[manager.requestSerializer setValue:url.host forHTTPHeaderField:@"host"];
// 注: カスタムの NSURLProtocol を使用しているため、SNI と証明書の検証はプロトコルレイヤーで処理されます。
// リクエストを直接送信します。
[manager GET:requestUrl parameters:nil headers:nil progress:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
NSData *data = [[NSData alloc] initWithData:responseObject];
NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (completionHandler) {
completionHandler([NSString stringWithFormat:@"Request successful: %@", dataStr]);
}
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (completionHandler) {
completionHandler([NSString stringWithFormat:@"Request failed: %@", error.localizedDescription]);
}
}];
}
注: SNI シナリオでは、証明書の検証とドメイン名の処理は NSURLProtocol レイヤーで自動的に処理されます。個別の証明書検証コールバックを設定する必要はありません。
サンプル実装については、Alibaba Cloud の httpdns_ios_demo にある HttpDnsNSURLProtocolImpl.m ファイルをご参照ください。必要に応じて、この実装を修正または再利用できます。
4. 例
4.1 基本的な HTTPS リクエスト
// 標準 HTTP リクエスト (非 SNI シナリオ)
[AFNHttpsScenario httpDnsQueryWithURL:@"http://example.com/api/data"
completionHandler:^(NSString *message) {
NSLog(@"Request result: %@", message);
}];
4.2 SNI シナリオでのリクエスト
// SNI シナリオでのリクエスト
[AFNHttpsWithSNIScenario httpDnsQueryWithSNIURL:@"https://example.com/api/data"
completionHandler:^(NSString *message) {
NSLog(@"Request result: %@", message);
}];
5. まとめ
HTTPDNS を AFNetworking と統合するためのコアステップは次のとおりです。
AFHTTPSessionManager の初期化: 基本的なネットワークパラメーターを設定します。
HTTPDNS ドメイン名の名前解決の実行: ドメイン名を置き換えるための IP アドレスを取得します。
ネットワークリクエストの送信: Host ヘッダーを設定し、HTTPS 証明書の検証を処理します。
キーポイント
Host ヘッダー: サーバーがリクエストされたドメイン名を正しく識別できるようにします。
証明書の検証: 証明書の検証には、IP アドレスではなく、元のドメイン名を使用します。
SNI の処理: 複雑なシナリオでの自動処理には NSURLProtocol を使用します。
フォールバックポリシー: HTTPDNS の名前解決が失敗した場合は、システムの DNS にフォールバックします。
完全なサンプルコードについては、「HTTPDNS iOS Demo」をご参照ください。