本文档介绍在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_demo中HttpDnsNSURLProtocolImpl.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的核心步骤:
初始化Session - 配置基础网络参数和代理
HTTPDNS域名解析 - 获取IP地址替换域名
发送网络请求 - 设置Host头并处理HTTPS证书校验
关键要点
Host头设置:确保服务器能正确识别请求的域名
证书校验:使用原始域名而非IP进行证书验证
SNI处理:复杂场景使用NSURLProtocol自动处理
降级策略:HTTPDNS解析失败时回退到系统DNS
完整的示例代码请参考:HTTPDNS iOS Demo
该文章对您有帮助吗?