All Products
Search
Document Center

HTTPDNS:Praktik terbaik untuk menggunakan HTTPDNS dengan Alamofire pada iOS

Last Updated:Nov 10, 2025

Dokumen ini menjelaskan cara mengintegrasikan HTTPDNS dengan Alamofire pada klien iOS.

1. Ikhtisar

Alamofire adalah kerangka kerja jaringan populer untuk pengembangan iOS yang menyediakan API elegan dan fitur-fitur canggih. Dokumen ini menjelaskan cara mengintegrasikan HTTPDNS ke dalam proyek yang menggunakan Alamofire.

Untuk informasi dasar mengenai HTTPDNS dan tantangan teknis penggunaannya di iOS, lihat Gunakan HTTPDNS dalam skenario iOS native.

2. Integrasi untuk skenario HTTP standar dan HTTPS non-SNI

Metode ini berlaku untuk skenario HTTP standar dan HTTPS non-SNI.

2.1 Buat sesi kustom

import Alamofire
import AlicloudHttpDNS

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

2.2 Selesaikan nama domain dengan 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 Kirim permintaan jaringan dan tangani validasi sertifikat

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 {
        // Ganti nama domain dengan alamat IP yang telah diselesaikan.
        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
    }
    
    // Kirim permintaan jaringan.
    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) {
    // Penting: Tetapkan header Host untuk memastikan server mengenali nama domain dengan benar.
    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 = "Permintaan HTTP gagal dengan kesalahan: \(error.localizedDescription)"
            }
            
            completionHandler(responseStr)
        }
}

// Sesuaikan SessionDelegate untuk menangani validasi sertifikat.
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") ?? ""
        // Penting: Gunakan nama domain asli untuk validasi sertifikat.
        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
        }
        
        // Buat kebijakan validasi sertifikat.
        var policies = [SecPolicy]()
        if let host = host {
            policies.append(SecPolicyCreateSSL(true, host as CFString))
        } else {
            policies.append(SecPolicyCreateBasicX509())
        }
        
        // Ikat kebijakan validasi ke sertifikat server.
        SecTrustSetPolicies(serverTrust, policies as CFTypeRef)
        
        // Evaluasi kepercayaan sertifikat.
        var result: SecTrustResultType = .invalid
        if SecTrustEvaluate(serverTrust, &result) == errSecSuccess {
            return result == .unspecified || result == .proceed
        } else {
            return false
        }
    }
}

3. Metode 2: Skenario HTTPS + SNI

Metode ini berlaku untuk skenario yang memerlukan dukungan Indikasi Nama Server (SNI), seperti menggunakan CDN atau berbagi alamat IP di antara beberapa nama domain.

3.1 Konfigurasi sesi yang mendukung SNI

import Alamofire
import AlicloudHttpDNS

class AlamofireHttpsWithSNIScenario {
    
    static let sharedSession: Session = {
        let configuration = URLSessionConfiguration.af.default
        // Penting: Daftarkan NSURLProtocol kustom untuk menangani SNI.
        configuration.protocolClasses = [HttpDnsNSURLProtocolImpl.classForCoder()]
        return Session(configuration: configuration)
    }()
}

3.2 Selesaikan nama domain dengan 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 Kirim permintaan jaringan dalam skenario 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
    }
    
    // Kirim permintaan jaringan.
    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) {
    // Tetapkan header Host.
    var header = HTTPHeaders()
    header.add(name: "host", value: host)
    
    // Catatan: SNI dan validasi sertifikat ditangani di lapisan protokol karena NSURLProtocol kustom digunakan. Anda dapat langsung mengirim permintaan.
    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 = "Permintaan HTTP gagal dengan kesalahan: \(error.localizedDescription)"
            }
            
            completionHandler(responseStr)
        }
}

Catatan: Dalam skenario SNI, validasi sertifikat dan penanganan nama domain diproses secara otomatis di lapisan NSURLProtocol. Anda tidak perlu mengonfigurasi callback validasi sertifikat tambahan.

Untuk implementasi contoh, lihat HttpDnsNSURLProtocolImpl.m dalam httpdns_ios_demo Alibaba Cloud. Anda dapat memodifikasi atau menggunakan kembali contoh ini sesuai kebutuhan.

4. Contoh

4.1 Permintaan HTTPS dasar

// Permintaan HTTPS standar (skenario non-SNI)
AlamofireHttpsScenario.httpDnsQueryWithURL(originalUrl: "https://example.com/api/data") { 
  message inprint("Hasil permintaan: \(message)")
}

4.2 Permintaan skenario SNI

// Permintaan skenario SNI (misalnya, CDN)
AlamofireHttpsWithSNIScenario.httpDnsQueryWithURL(originalUrl: "https://cdn.example.com/api/data") { 
  message inprint("Hasil permintaan: \(message)")
}

5. Ringkasan

Langkah-langkah inti untuk mengintegrasikan HTTPDNS dengan Alamofire adalah:

  1. Inisialisasi sesi – Konfigurasikan parameter jaringan dasar dan proxy.

  2. Selesaikan nama domain dengan HTTPDNS – Dapatkan alamat IP untuk menggantikan nama domain.

  3. Kirim permintaan jaringan – Tetapkan header Host dan tangani validasi sertifikat HTTPS.

Poin-poin penting

  • Header Host: Pastikan server dapat mengenali nama domain yang diminta dengan benar.

  • Validasi sertifikat: Gunakan nama domain asli, bukan alamat IP, untuk validasi sertifikat.

  • Penanganan SNI: Gunakan NSURLProtocol untuk penanganan otomatis dalam skenario kompleks.

  • Kebijakan fallback: Kembali ke DNS sistem jika resolusi HTTPDNS gagal.

Untuk kode contoh lengkap, lihat HTTPDNS iOS Demo.