すべてのプロダクト
Search
ドキュメントセンター

Alibaba Cloud DNS:HTTP および HTTPS (SNI を含む) シナリオにおける iOS のダイレクト IP 接続ソリューション

最終更新日:Nov 09, 2025

この Topic では、HTTP および HTTPS (サーバ名表示 (SNI) を含む) シナリオで iOS 上にダイレクト IP 接続を確立するためのソリューションについて説明します。

概要

HTTP は、分散型、協調型、ハイパーメディア情報システム向けのアプリケーション層プロトコルです。これは World Wide Web のデータ通信の基盤です。HTTP はクライアントサーバーモデルで動作します。これにより、ブラウザは Web サーバーに Web ページのコンテンツをリクエストし、その結果を表示できます。HTTP はデータをプレーンテキストで送信し、暗号化メカニズムを提供しません。これにより、データは送信中に盗聴、改ざん、または中間者攻撃に対して脆弱になります。

HTTPS は、コンピューターネットワーク上で安全な通信を行うために使用される HTTP の拡張機能です。Secure Sockets Layer (SSL)/Transport Layer Security (TLS) を使用して、安全な通信チャネルを確立し、データパケットを暗号化します。HTTPS の主な目的は、Web サイトサーバーを認証し、交換されるデータのプライバシーと整合性を保護することです。TLS はトランスポート層の暗プロトコルであり、SSL の後継です。HTTPS には、一般的な HTTPS と SNI の 2 つの一般的なシナリオがあります。

サーバ名表示 (SNI) は、サーバーとクライアントの通信を改善する SSL および TLS プロトコルへの拡張機能です。これは主に、単一のサーバーが複数のドメイン名にサービスを提供する場合に使用されます。

  • HTTP シナリオ

    HTTP シナリオでは、ネットワークリンクに SSL/TLS ハンドシェイクは含まれず、証明書の検証も必要ありません。ダイレクト IP 接続を実装するには、リクエスト URL のホストを IP アドレスに置き換え、HTTP ヘッダーの Host フィールドを元のドメイン名に設定します。

  • 一般的な HTTPS シナリオ

    一般的な HTTPS シナリオでは、リクエスト URL のホストを IP アドレスに置き換え、HTTP ヘッダーの Host フィールドを元のドメイン名に設定することで、ダイレクト IP 接続を確立できます。次に、証明書の検証中に、IP アドレスを元のドメイン名に置き換える必要があります。

  • SNI シナリオ

    SNI(単一の IP と複数の HTTPS 証明書)シナリオでは、iOS の上位層ネットワークライブラリ NSURLConnection/NSURLSessionSNI フィールド を構成するためのインターフェイスを提供していません。したがって、IP 直接接続ネットワークリクエスト の適応ソリューションを実装するには、CFNetwork などのソケットレベルの下位層ネットワークライブラリが必要です。ただし、CFNetwork ベースのソリューションでは、開発者はデータの送受信、リダイレクト、デコード、キャッシュなどの問題を考慮する必要があります(CFNetwork は非常に低レベルのネットワーク実装です)。開発者は、このシナリオの使用リスクを適切に評価することをお勧めします。開発者は、iOS14 ネイティブ暗号化 DNS ソリューション を参照して SNI シナリオの問題を解決することをお勧めします。

重要

サーバー側で CDN サービスを使用する場合は、SNI シナリオソリューションを参照してください。

ベストプラクティス

  • HTTP シナリオのソリューション

1. リクエスト URL のホストを IP アドレスに置き換えます。

2. HTTP ヘッダーの Host フィールドを元のドメイン名に設定します。

// リクエストを構築します。
- (NSMutableURLRequest *)createRequest {
    // ドメイン名が example.com で、HTTPDNS によって解決された IP アドレスが 1.2.3.4 であると仮定します。
    NSString *urlString = @"http://example.com/api";
    // 1. リクエスト URL のホストを IP アドレスに置き換えます。
    NSString *httpDnsString = [urlString stringByReplacingOccurrencesOfString:@"example.com" withString:@"1.2.3.4"];
    NSURL *httpDnsURL = [NSURL URLWithString:httpDnsString];
    NSMutableURLRequest *mutableReq = [NSMutableURLRequest requestWithURL:httpDnsURL];
    // 2. HTTP ヘッダーの Host フィールドを元のドメイン名に設定します。
    [mutableReq setValue:@"example.com" forHTTPHeaderField:@"Host"];
    return mutableReq;
}

  • 一般的な HTTPS シナリオのソリューション

1. リクエスト URL のホストを IP アドレスに置き換えます。

2. HTTP ヘッダーの Host フィールドを元のドメイン名に設定します。

3. 証明書検証中のドメイン不一致の問題を解決するには、IP アドレスを元のドメイン名に置き換えます。

次の例は、NSURLSession および NSURLConnection API が呼び出されたときのソリューションを示しています。

// リクエストを構築します。
- (NSMutableURLRequest *)createRequest {
    // ドメイン名が example.com で、HTTPDNS によって解決される IP アドレスが 1.2.3.4 であると仮定します。
    NSString *urlString = @"https://example.com/api";
    // 1. リクエスト URL のホストを IP アドレスに置き換えます。
    NSString *httpDnsString = [urlString stringByReplacingOccurrencesOfString:@"example.com" withString:@"1.2.3.4"];
    NSURL *httpDnsURL = [NSURL URLWithString:httpDnsString];
    NSMutableURLRequest *mutableReq = [NSMutableURLRequest requestWithURL:httpDnsURL];
    // 2. HTTP ヘッダーの Host フィールドを元のドメイン名に設定します。
    [mutableReq setValue:@"example.com" forHTTPHeaderField:@"Host"];
    return mutableReq;
}

// 3. 証明書の検証では、IP アドレスの代わりに元のドメイン名を使用します。

/*
 * NSURLConnection
 */
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if (!challenge) {
        return;
    }
    /*
     * HTTPDNS を使用する場合、URL のホストは IP アドレスに設定されます。
     * HTTP ヘッダーから実際のドメイン名を取得します。
     */
    NSString* host = [[self.request allHTTPHeaderFields] objectForKey:@"host"];
    if (!host) {
        host = self.request.URL.host;
    }
    /*
     * チャレンジの認証方式が NSURLAuthenticationMethodServerTrust であるかどうかを確認します。
     * この認証プロセスは HTTPS モードで発生します。
     * 認証方式が設定されていない場合は、デフォルトのネットワークリクエストフローが使用されます。
     */
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) {
            /*
             * 検証後、NSURLCredential を構築して送信者に送信します。
             */
            NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
        } else {
            /*
             * 検証が失敗した場合は、デフォルトのプロセスに進みます。
             */
            [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
        }
    } else {
        /*
         * 他の認証方式の場合は、直接プロセスに進みます。
         */
        [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
    }
}
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
/*
 * NSURLSession
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler
{
    if (!challenge) {
        return;
    }
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    NSURLCredential *credential = nil;
    /*
     * 元のドメイン名を取得します。
     */
    NSString* host = [[self.request allHTTPHeaderFields] objectForKey:@"host"];
    if (!host) {
        host = self.request.URL.host;
    }
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) {
            disposition = NSURLSessionAuthChallengeUseCredential;
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    } else {
        disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    }
    // 他のチャレンジにはデフォルトの認証方式を使用します。
    completionHandler(disposition,credential);
}	

- (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);
    /*
     * 現在の serverTrust が信頼できるかどうかを評価します。
     * Apple Inc. は、結果が kSecTrustResultUnspecified または kSecTrustResultProceed の場合、serverTrust を検証できることを推奨しています。
     * 詳細については、「https://developer.apple.com/library/ios/technotes/tn2232/_index.html」をご参照ください。
     * SecTrustResultType の詳細については、「SecTrust.h」をご参照ください。
     */
    SecTrustResultType result;
    SecTrustEvaluate(serverTrust, &result);
    return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
}

一般的なシナリオの AFNetworking ネットワークライブラリでの IP 直接接続については、次のソリューションを参照してください。

// この戻り値を AFHTTPSessionManager のリクエスト URL として使用します。
+(NSString *)getIPStringFromAliCloudDNSResolverWithURLString: (NSString *)URLString manager: (AFHTTPSessionManager *)manager {
    NSURL *originUrl = [NSURL URLWithString :URLString];
    NSString *host = originUrl.host;
    NSString *ip= [[DNSResolver share] getIpsByCacheWithDomain:host andExpiredIPEnabled:YES].firstObject;
    NSString *ipURLString = URLString;
    if (ip) {
        // 1. リクエスト URL のホストを IP アドレスに置き換えます。
        ipURLString = [URLString stringByReplacingOccurrencesOfString:host withString:ip];
        // 2. HTTP ヘッダーの Host フィールドを元のドメイン名に設定します。
        [manager.requestSerializer setValue:originUrl.host forHTTPHeaderField:@"Host"];
        __weak typeof (AFHTTPSessionManager *) weakSessionManager = manager;
        // 3. 証明書の検証中に、IP アドレスを元のドメイン名に置き換えます。
        [manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession*_Nonnullsession,
                                                                                                        NSURLAuthenticationChallenge * _Nonnull challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential) {
            NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            // 元のドメイン名を取得します。
            NSString *host = [[weakSessionManager.requestSerializer HTTPRequestHeaders] objectForKey:@"host" ];
            if (!host) {
                host = challenge.protectionSpace.host;
            }
            if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
                if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                    *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            }
            return disposition;
        }];
    }
        return ipURLString;
}

+(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);
 }
 

重要

このソリューションに基づいてネットワークリクエストを開始し、SSL 検証エラー(iOS システムエラー kCFStreamErrorDomainSSL, -9813; このサーバーの証明書は無効です など)が報告された場合は、アプリケーションシナリオが SNI(単一の IP と複数の HTTPS ドメイン名)であるかどうかを確認してください。

  • SNI HTTPS シナリオのソリューション

1. カスタム NSURLProtocol ソリューション

SNI シナリオのソリューションについては、デモサンプルプロジェクトのソースコード を参照してください。次のセクションでは、現在課題となっている点と対処方法について説明します。

POST リクエストのサポート

NSURLProtocol を使用して NSURLSession リクエストをインターセプトする場合、リクエスト本文が欠落しています。この問題を解決するには、次の方法を使用できます。

HTTPBodyStream を使用してリクエスト本文を取得し、本文に値を割り当てます。サンプルコード:

//
//  NSURLRequest+NSURLProtocolExtension.h
//
//
#import <Foundation/Foundation.h>
@interface NSURLRequest (NSURLProtocolExtension)
- (NSURLRequest *)alidns_getPostRequestIncludeBody;
@end
//
//  NSURLRequest+NSURLProtocolExtension.h
//
//
#import "NSURLRequest+NSURLProtocolExtension.h"
@implementation NSURLRequest (NSURLProtocolExtension)
- (NSURLRequest *)alidns_getPostRequestIncludeBody {
    return [[self alidns_getMutablePostRequestIncludeBody] copy];
}
- (NSMutableURLRequest *)alidns_getMutablePostRequestIncludeBody {
    NSMutableURLRequest * req = [self mutableCopy];
    if ([self.HTTPMethod isEqualToString:@"POST"]) {
        if (!self.HTTPBody) {
            NSInteger maxLength = 1024;
            uint8_t d[maxLength];
            NSInputStream *stream = self.HTTPBodyStream;
            NSMutableData *data = [[NSMutableData alloc] init];
            [stream open];
            BOOL endOfStreamReached = NO;
            //[stream hasBytesAvailable] を使用して while ループを決定することはできません。システムが画像ファイルを処理する場合、システムは [stream hasBytesAvailable] に対して YES を返します。その結果、無限 while ループが発生します。
            while (!endOfStreamReached) {
                NSInteger bytesRead = [stream read:d maxLength:maxLength];
                if (bytesRead == 0) { //システムがファイルの末尾まで読み取る
                    endOfStreamReached = YES;
                } else if (bytesRead == -1) { //システムがファイルを読み取るときにエラーが発生する
                    endOfStreamReached = YES;
                } else if (stream.streamError == nil) {
                    [data appendBytes:(void *)d length:bytesRead];
                }
            }
            req.HTTPBody = [data copy];
            [stream close];
        }
    }
    return req;
}
@end

使用方法:

リクエストをインターセプトするために使用される NSURLProtocol サブクラスで +canonicalRequestForRequest: メソッドを実装し、request オブジェクトを処理します。

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return [request alidns_getPostRequestIncludeBody];
}

次に、関連メソッドの機能について説明します。

//NSURLProtocol.h
/*!
 *  @method: NSURLProtocol インスタンスを作成する。NSURLProtocol が登録されると、すべての NSURLConnections はこのメソッドを介して HTTP リクエストを保持するかどうかを確認します。
 @param:
 @return: YES: HTTP リクエストを保持する       NO: HTTP リクエストを保持しない
 */
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
/*!
 *  @method: NSURLProtocol 抽象クラスを実装する必要があります。ほとんどの場合、入力および出力リクエストで使用されるプロトコルは一致している必要があります。これは基本的な要件です。このように、リクエストを直接返すことができます。ほとんどの場合、リクエストを変更する必要はありません。リクエストを変更すると、新しいリクエストが返されます。たとえば、HTTP リクエストにタイトルを追加して新しいリクエストを生成すると、新しいリクエストが返されます。
 @param: ローカル HttpRequest: request
 @return: 直接転送
 */
+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest *)request

概要:

  • +[NSURLProtocol canInitWithRequest:] は、インターセプトする必要があるネットワークリクエストをフィルタリングする役割を担います

  • +[NSURLProtocol canonicalRequestForRequest:] は、インターセプトする必要があるネットワークリクエストの NSURLRequest を再構築する役割を担います。

注:+[NSURLProtocol canonicalRequestForRequest:] の実行条件は、+[NSURLProtocol canInitWithRequest:] の戻り値が YES であることです。

NSURLSession リクエストをインターセプトする場合、リクエストをインターセプトするために使用される NSURLProtocol サブクラスを NSURLSessionConfiguration に追加する必要があります。次のようにします。

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSArray *protocolArray = @[ [CUSTOMEURLProtocol class] ];
configuration.protocolClasses = protocolArray;
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];

SNI シナリオの AFNetworking ネットワークライブラリでの IP 直接接続については、次のソリューションを参照してください。

    // NSURLSessionConfiguration インスタンスを作成する
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    
    // カスタム HTTP DNS 解決プロトコルを使用するように設定する
    config.protocolClasses = @[[CFHTTPDNSHTTPProtocol class]];

    // AFHTTPSessionManager を初期化する
    AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:config];
    sessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];

    // データタスクを作成する
    NSURLSessionDataTask *task = [sessionManager dataTaskWithHTTPMethod:@"GET"
                                                             URLString:@"リクエスト URL"
                                                            parameters:nil
                                                               headers:nil
                                                            uploadProgress:^(NSProgress *uploadProgress) {
        NSLog(@"アップロードの進捗状況: %@", uploadProgress.localizedDescription);
    } downloadProgress:^(NSProgress *downloadProgress) {
        NSLog(@"ダウンロードの進捗状況: %@", downloadProgress.localizedDescription);
    } success:^(NSURLSessionDataTask *task, id responseObject) {
        // 成功レスポンスを処理する
        NSLog(@"成功: %@", responseObject);
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
        // エラーを処理する
        NSLog(@"失敗: %@", error.localizedDescription);
    }];
    
    // オブジェクトダウンロードタスクを開始する
    [task resume];

2. その他の下位層ネットワークライブラリソリューション

libcurl を例にとると、libcurl / cURL は少なくとも 7.18.1(2008 年 3 月 30 日)で SNI をサポートする SSL/TLS ツールキットを使用してコンパイルされており、curl には指定された IP アドレスを使用して HTTPS Web サイトにアクセスできる --resolve メソッドがあります。

iOS のサンプルコード:

// {HTTPS ドメイン名}:443:{IP アドレス}
NSString *curlHost = ...;
_hosts_list = curl_slist_append(_hosts_list, curlHost.UTF8String);
curl_easy_setopt(_curl, CURLOPT_RESOLVE, _hosts_list);

ここで、curlHost{HTTPS ドメイン名}:443:{IP アドレス} のようになります

_hosts_list は構造体型の hosts_list で、IP とホストの間の複数のマッピングを設定できます。curl_easy_setopt メソッドは CURLOPT_RESOLVE を渡して HTTPS リクエストでこのマッピングを設定します。その後、SNI が実装されます。

概要

ソリューション

適用シナリオ

長所

短所

1. URL の置き換え

2. Host の設定

HTTP シナリオ

最も簡単な方法

プレーンテキストの HTTP プロトコルにのみ適しています

1. URL の置き換え

2. Host の設定

3. 証明書検証中に IP を元のドメイン名に置き換える

一般的な HTTPS シナリオ (非 SNI)

簡単な統合、一般的なシステムネットワークライブラリ、NSURLSession、

NSURLConnection、

AFHTTPSessionManager のサポート

SNI をサポートしていません

カスタム NSURLProtocol

すべてのシナリオ

完全に低レベルのシステム API に基づいています

低レベルのネットワークライブラリに基づいています。開発とメンテナンスのコストが高くなります。データ送受信、リダイレクト、デコード、キャッシュを処理する必要があります。

libcurl

すべてのシナリオ

クロスプラットフォーム

成熟したコミュニティと広範なドキュメント。

SNI を設定できます。

強力なクロスプラットフォーム互換性。

C 言語 API は iOS 開発者にとって学習曲線があります。

Cookie、リダイレクト、キャッシュなどの問題を処理する必要があります。