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

HTTPDNS:iOS で Alamofire と HTTPDNS を使用するためのベストプラクティス

最終更新日:Nov 09, 2025

このドキュメントでは、iOS クライアントで HTTPDNS を Alamofire と統合する方法について説明します。

1. 概要

Alamofire は、洗練された API と強力な機能を備えた、iOS 開発で人気のネットワークフレームワークです。このドキュメントでは、Alamofire を使用するプロジェクトに HTTPDNS を統合する方法について説明します。

HTTPDNS の基本情報と iOS での使用における技術的な課題の詳細については、「ネイティブ iOS シナリオで HTTPDNS を使用する」をご参照ください。

2. 標準 HTTP および非 SNI HTTPS シナリオの統合

このメソッドは、標準 HTTP および非 SNI HTTPS シナリオに適用されます。

2.1 カスタムセッションの作成

import Alamofire
import AlicloudHttpDNS

class AlamofireHttpsScenario {
    
    static let sharedSession: Session = {
        return Session(delegate: CustomerSessionDelegate())
    }()
}

2.2 HTTPDNS によるドメイン名の解決

class func resolveAvailableIp(host: String) -> String? {
    let httpDnsService = HttpDnsService.sharedInstance()
    let result = httpDnsService.resolveHostSyncNonBlocking(host, by: .auto)
    
    print("resolve host result: \(String(describing: result))")
    if result == nil {
        return nil
    }
    
    if result!.hasIpv4Address() {
        return result?.firstIpv4Address()
    } else if result!.hasIpv6Address() {
        return "[\(result!.firstIpv6Address())]"
    }
    return nil
}

2.3 ネットワークリクエストの送信と証明書検証の処理

class func httpDnsQueryWithURL(originalUrl: String, completionHandler: @escaping (_ message: String) -> Void) {
    var tipsMessage: String = ""guard let url = NSURL(string: originalUrl), let originalHost = url.host else {
        print("Error: invalid url: \(originalUrl)")
        return
    }
    
    let resolvedIpAddress = resolveAvailableIp(host: originalHost)
    
    var requestUrl = originalUrl
    if resolvedIpAddress != nil {
        // ドメイン名を解決済みの IP アドレスに置き換えます。
        requestUrl = requestUrl.replacingOccurrences(of: originalHost, with: resolvedIpAddress!)
        
        let log = "Resolve host(\(originalHost)) by HTTPDNS successfully, result ip: \(resolvedIpAddress!)"print(log)
        tipsMessage = log
    } else {
        let log = "Resolve host(\(originalHost) by HTTPDNS failed, keep original url to request"print(log)
        tipsMessage = log
    }
    
    // ネットワークリクエストを送信します。
    sendRequestWithURL(requestUrl: requestUrl, host: originalHost) { message in
        tipsMessage = tipsMessage + "\n\n" + message
        completionHandler(tipsMessage)
    }
}

class func sendRequestWithURL(requestUrl: String, host: String, completionHandler: @escaping (_ message: String) -> Void) {
    // 重要: サーバーがドメイン名を正しく識別できるように、Host ヘッダーを設定します。
    var header = HTTPHeaders()
    header.add(name: "host", value: host)
    
    sharedSession.request(requestUrl, method: .get, encoding: URLEncoding.default, headers: header)
        .validate()
        .response { response invar responseStr = ""switch response.result {
            case .success(let data):
                if let data = data, !data.isEmpty {
                    let dataStr = String(data: data, encoding: .utf8) ?? ""
                    responseStr = "HTTP Response: \(dataStr)"
                } else {
                    responseStr = "HTTP Response: [Empty Data]"
                }
            case .failure(let error):
                responseStr = "HTTP request failed with error: \(error.localizedDescription)"
            }
            
            completionHandler(responseStr)
        }
}

//証明書検証を処理するために SessionDelegate をカスタマイズします。
class CustomerSessionDelegate: SessionDelegate {
    
    override func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        
        var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
        var credential: URLCredential?
        
        let request = task.currentRequest
        let host = request?.value(forHTTPHeaderField: "host") ?? ""
        // 重要: 証明書検証には元のドメイン名を使用します。
        if !host.isEmpty {
            if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
                if evaluate(serverTrust: challenge.protectionSpace.serverTrust, host: host) {
                    disposition = .useCredential
                    credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
                } else {
                    disposition = .performDefaultHandling
                }
            } else {
                disposition = .performDefaultHandling
            }
        }
        completionHandler(disposition, credential)
    }
    
    func evaluate(serverTrust: SecTrust?, host: String?) -> Bool {
        guard let serverTrust = serverTrust else {
            return false
        }
        
        // 証明書検証ポリシーを作成します。
        var policies = [SecPolicy]()
        if let host = host {
            policies.append(SecPolicyCreateSSL(true, host as CFString))
        } else {
            policies.append(SecPolicyCreateBasicX509())
        }
        
        // 検証ポリシーをサーバー証明書にバインドします。
        SecTrustSetPolicies(serverTrust, policies as CFTypeRef)
        
        // 証明書の信頼性を評価します。
        var result: SecTrustResultType = .invalid
        if SecTrustEvaluate(serverTrust, &result) == errSecSuccess {
            return result == .unspecified || result == .proceed
        } else {
            return false
        }
    }
}

3. 方法 2: HTTPS + SNI シナリオ

このメソッドは、CDN の使用や複数のドメイン名間での IP アドレスの共有など、サーバ名表示 (SNI) のサポートを必要とするシナリオに適用されます。

3.1 SNI をサポートするセッションの設定

import Alamofire
import AlicloudHttpDNS

class AlamofireHttpsWithSNIScenario {
    
    static let sharedSession: Session = {
        let configuration = URLSessionConfiguration.af.default
        // 重要: SNI を処理するためにカスタム NSURLProtocol を登録します。
        configuration.protocolClasses = [HttpDnsNSURLProtocolImpl.classForCoder()]
        return Session(configuration: configuration)
    }()
}

3.2 HTTPDNS によるドメイン名の解決

class func resolveAvailableIp(host: String) -> String? {
    let httpDnsService = HttpDnsService.sharedInstance()
    let result = httpDnsService.resolveHostSyncNonBlocking(host, by: .auto)
    
    print("resolve host result: \(String(describing: result))")
    if result == nil {
        return nil
    }
    
    if result!.hasIpv4Address() {
        return result?.firstIpv4Address()
    } else if result!.hasIpv6Address() {
        return "[\(result!.firstIpv6Address())]"
    }
    return nil
}

3.3 SNI シナリオでのネットワークリクエストの送信

class func httpDnsQueryWithURL(originalUrl: String, completionHandler: @escaping (_ message: String) -> Void) {
    var tipsMessage: String = ""guard let url = NSURL(string: originalUrl), let originalHost = url.host else {
        print("Error: invalid url: \(originalUrl)")
        return
    }
    
    let resolvedIpAddress = resolveAvailableIp(host: originalHost)
    
    var requestUrl = originalUrl
    if resolvedIpAddress != nil {
        requestUrl = requestUrl.replacingOccurrences(of: originalHost, with: resolvedIpAddress!)
        
        let log = "Resolve host(\(originalHost)) by HTTPDNS successfully, result ip: \(resolvedIpAddress!)"print(log)
        tipsMessage = log
    } else {
        let log = "Resolve host(\(originalHost) by HTTPDNS failed, keep original url to request"print(log)
        tipsMessage = log
    }
    
    // ネットワークリクエストを送信します。
    sendRequestWithURL(requestUrl: requestUrl, host: originalHost) { message in
        tipsMessage = tipsMessage + "\n\n" + message
        completionHandler(tipsMessage)
    }
}

class func sendRequestWithURL(requestUrl: String, host: String, completionHandler: @escaping (_ message: String) -> Void) {
    // Host ヘッダーを設定します。
    var header = HTTPHeaders()
    header.add(name: "host", value: host)
    
    // 注: カスタム NSURLProtocol が使用されているため、SNI と証明書検証はプロトコルレイヤーで処理されます。リクエストを直接送信できます。
    sharedSession.request(requestUrl, method: .get, encoding: URLEncoding.default, headers: header)
        .validate()
        .response { response invar responseStr = ""switch response.result {
            case .success(let data):
                if let data = data, !data.isEmpty {
                    let dataStr = String(data: data, encoding: .utf8) ?? ""
                    responseStr = "HTTP Response: \(dataStr)"
                } else {
                    responseStr = "HTTP Response: [Empty Data]"
                }
            case .failure(let error):
                responseStr = "HTTP request failed with error: \(error.localizedDescription)"
            }
            
            completionHandler(responseStr)
        }
}

: SNI シナリオでは、証明書検証とドメイン名の処理は NSURLProtocol レイヤーで自動的に処理されます。追加の証明書検証コールバックを設定する必要はありません。

実装例については、Alibaba Cloud httpdns_ios_demo の「HttpDnsNSURLProtocolImpl.m」をご参照ください。必要に応じて、このサンプルを変更または再利用できます。

4. 例

4.1 基本的な HTTPS リクエスト

// 標準 HTTPS リクエスト (非 SNI シナリオ)
AlamofireHttpsScenario.httpDnsQueryWithURL(originalUrl: "https://example.com/api/data") { 
  message inprint("Request result: \(message)")
}

4.2 SNI シナリオのリクエスト

// SNI シナリオのリクエスト (例: CDN)
AlamofireHttpsWithSNIScenario.httpDnsQueryWithURL(originalUrl: "https://cdn.example.com/api/data") { 
  message inprint("Request result: \(message)")
}

5. まとめ

HTTPDNS を Alamofire と統合するための主要なステップは次のとおりです。

  1. セッションの初期化 - 基本的なネットワークパラメーターとプロキシを設定します。

  2. HTTPDNS によるドメイン名の解決 - ドメイン名を置き換えるための IP アドレスを取得します。

  3. ネットワークリクエストの送信 - Host ヘッダーを設定し、HTTPS 証明書検証を処理します。

キーポイント

  • Host ヘッダー: サーバーがリクエストされたドメイン名を正しく識別できるようにします。

  • 証明書検証: 証明書検証には、IP アドレスではなく元のドメイン名を使用します。

  • SNI 処理: 複雑なシナリオでの自動処理には NSURLProtocol を使用します。

  • フォールバックポリシー: HTTPDNS の解決に失敗した場合は、システムの DNS にフォールバックします。

完全なサンプルコードの詳細については、「HTTPDNS iOS Demo」をご参照ください。