This document describes how to integrate HTTPDNS with Alamofire on an iOS client.
1. Overview
Alamofire is a popular networking framework for iOS development that provides an elegant API and powerful features. This document describes how to integrate HTTPDNS into a project that uses Alamofire.
For basic information about HTTPDNS and the technical challenges of using it on iOS, see Use HTTPDNS in native iOS scenarios.
2. Integration for standard HTTP and non-SNI HTTPS scenarios
This method applies to standard HTTP and non-SNI HTTPS scenarios.
2.1 Create a custom session
import Alamofire
import AlicloudHttpDNS
class AlamofireHttpsScenario {
static let sharedSession: Session = {
return Session(delegate: CustomerSessionDelegate())
}()
}
2.2 Resolve domain names with 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 Send network requests and handle certificate validation
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 {
// Replace the domain name with the resolved IP address.
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
}
// Send the network request.
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) {
// Important: Set the Host header to ensure the server correctly identifies the domain name.
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)
}
}
// Customize SessionDelegate to handle certificate validation.
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") ?? ""
// Important: Use the original domain name for certificate validation.
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
}
// Create a certificate validation policy.
var policies = [SecPolicy]()
if let host = host {
policies.append(SecPolicyCreateSSL(true, host as CFString))
} else {
policies.append(SecPolicyCreateBasicX509())
}
// Bind the validation policy to the server certificate.
SecTrustSetPolicies(serverTrust, policies as CFTypeRef)
// Evaluate the certificate trust.
var result: SecTrustResultType = .invalid
if SecTrustEvaluate(serverTrust, &result) == errSecSuccess {
return result == .unspecified || result == .proceed
} else {
return false
}
}
}
3. Method 2: HTTPS + SNI scenario
This method applies to scenarios that require Server Name Indication (SNI) support, such as using a CDN or sharing an IP address among multiple domain names.
3.1 Configure a session that supports SNI
import Alamofire
import AlicloudHttpDNS
class AlamofireHttpsWithSNIScenario {
static let sharedSession: Session = {
let configuration = URLSessionConfiguration.af.default
// Important: Register a custom NSURLProtocol to handle SNI.
configuration.protocolClasses = [HttpDnsNSURLProtocolImpl.classForCoder()]
return Session(configuration: configuration)
}()
}
3.2 Resolve domain names with 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 Send network requests in an SNI scenario
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
}
// Send the network request.
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) {
// Set the Host header.
var header = HTTPHeaders()
header.add(name: "host", value: host)
// Note: SNI and certificate validation are handled at the protocol layer because a custom NSURLProtocol is used. You can send the request directly.
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)
}
}
Note: In an SNI scenario, certificate validation and domain name handling are automatically processed at the NSURLProtocol layer. You do not need to configure an extra certificate validation callback.
For a sample implementation, see HttpDnsNSURLProtocolImpl.m in the Alibaba Cloud httpdns_ios_demo. You can modify or reuse this sample as needed.
4. Examples
4.1 Basic HTTPS request
// Standard HTTPS request (non-SNI scenario)
AlamofireHttpsScenario.httpDnsQueryWithURL(originalUrl: "https://example.com/api/data") {
message inprint("Request result: \(message)")
}
4.2 SNI scenario request
// SNI scenario request (for example, a CDN)
AlamofireHttpsWithSNIScenario.httpDnsQueryWithURL(originalUrl: "https://cdn.example.com/api/data") {
message inprint("Request result: \(message)")
}
5. Summary
The core steps to integrate HTTPDNS with Alamofire are:
Initialize the session - Configure basic network parameters and the proxy.
Resolve the domain name with HTTPDNS - Obtain the IP address to replace the domain name.
Send the network request - Set the Host header and handle HTTPS certificate validation.
Key points
Host header: Ensure the server can correctly identify the requested domain name.
Certificate validation: Use the original domain name, not the IP address, for certificate validation.
SNI handling: Use NSURLProtocol for automatic handling in complex scenarios.
Fallback policy: Fall back to the system DNS if HTTPDNS resolution fails.
For the complete sample code, see the HTTPDNS iOS Demo.