全部產品
Search
文件中心

HTTPDNS:iOS端HTTPDNS+Alamofire最佳實務

更新時間:Oct 21, 2025

本文檔介紹在iOS用戶端上使用Alamofire接入HTTPDNS的方案。

1. 概述

Alamofire是iOS開發中廣泛使用的網路請求架構,提供了優雅的API和強大的功能。本文檔介紹如何在使用Alamofire的專案中整合HTTPDNS。

關於HTTPDNS的基礎知識和在iOS上使用時遇到的技術挑戰,請先參考:iOS端Native情境使用HTTPDNS

2. 普通HTTP情境、HTTPS+非SNI情境接入方案

適用於普通HTTP或HTTPS + 非SNI這兩種情境。

2.1 建立自訂Session

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. 方案二:HTTPS + SNI情境

適用於需要SNI支援的情境,如CDN或多網域名稱共用IP的情況。

3.1 配置支援SNI的Session

import Alamofire
import AlicloudHttpDNS

class AlamofireHttpsWithSNIScenario {
    
    static let sharedSession: Session = {
        let configuration = URLSessionConfiguration.af.default
        // 關鍵:註冊自訂NSURLProtocol來處理SNI
        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和認證驗證已在Protocol層處理// 直接發送請求即可
    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層自動處理,無需額外配置認證校正回調。

如果需要參考樣本,阿里雲提供了httpdns_ios_demoHttpDnsNSURLProtocolImpl.m的樣本實現,可根據業務需求進行修改或複用。

4. 使用樣本

4.1 基礎HTTPS請求

// 普通HTTPS請求(非SNI情境)
AlamofireHttpsScenario.httpDnsQueryWithURL(originalUrl: "https://example.com/api/data") { 
  message inprint("請求結果: \(message)")
}

4.2 SNI情境請求

// SNI情境請求(如CDN)
AlamofireHttpsWithSNIScenario.httpDnsQueryWithURL(originalUrl: "https://cdn.example.com/api/data") { 
  message inprint("請求結果: \(message)")
}

5. 總結

Alamofire整合HTTPDNS的核心步驟:

  1. 初始化Session - 配置基礎網路參數和代理

  2. HTTPDNS網域名稱解析 - 擷取IP地址替換網域名稱

  3. 發送網路請求 - 設定Host頭並處理HTTPS認證校正

關鍵要點

  • Host頭設定:確保伺服器能正確識別請求的網域名稱

  • 認證校正:使用原始網域名稱而非IP進行認證驗證

  • SNI處理:複雜情境使用NSURLProtocol自動處理

  • 降級策略:HTTPDNS解析失敗時回退到系統DNS

完整的範例程式碼請參考:HTTPDNS iOS Demo