EMAS iOS网络解决方案是阿里云EMAS团队为iOS开发者提供的完整网络库适配方案,能够与阿里云HTTPDNS配合使用,为iOS应用提供高性能、稳定可靠的网络服务。
目前EMAS提供两种网络架构方案,您可以根据应用需求选择合适的方案:
适用场景:在 NSURLSession 接入 HTTPDNS,且需要兼容 iOS 10.0+ 全量系统版本、获得更精细的请求控制与统一的性能指标采集时,优先使用 EMASCurl。
适用场景:在 iOS 17.0+ 系统为 WKWebView 配置代理与 HTTPDNS 能力。低于 iOS 17 的系统不支持 WKWebView 代理。
根据 Apple 官方统计(截至 2025 年 6 月 4 日),iOS 17+ 已占较高比例。考虑到 HTTPDNS 为 WKWebView 场景带来防劫持、调度精准、解析及时生效等非功能性提升,建议在使用本方案,在 iOS 17+ 的系统上支持HTTPDNS,iOS 16 及以下的版本也不会带来副作用。随着长尾用户逐渐升级到更新的系统,最终所有用户都可以享受到 HTTPDNS 带来的收益。
| 特性 | EMASCurl | EMASLocalProxy |
|---|---|---|
| iOS版本要求 | iOS 10.0+ | iOS 17.0+(<17 有兼容差异) |
| NSURLSession支持 | ✅ 协议拦截 | ✅ iOS17+ 原生代理;<17 仅HTTPS |
| WKWebView支持 | ❌ | ✅ iOS17+ 原生代理;<17 ❌ |
| 配置复杂度 | 中等 | 简单 |
| HTTPDNS集成 | ✅ | ✅ |
| 维护成本 | 中等 | 低 |
- EMAS iOS网络解决方案
EMASCurl是阿里云EMAS团队提供的基于libcurl的iOS平台网络库框架,通过NSURLProtocol拦截机制为iOS应用提供高性能的网络服务。EMASCurl具有以下特性:
- 广泛兼容:支持iOS 10.0+系统版本
- 协议拦截:通过NSURLProtocol拦截网络请求
- HTTP/2支持:基于libcurl的HTTP/2实现
- 丰富功能:提供缓存、性能监控、SSL配置等功能
- HTTPDNS集成:与阿里云HTTPDNS服务深度集成
- 精细控制:提供详细的网络请求控制选项
在您的Podfile文件中添加如下依赖:
source 'https://github.com/aliyun/aliyun-specs.git'
target 'yourAppTarget' do
use_framework!
pod 'EMASCurl', 'x.x.x'
end当前最新版本:1.4.3
在您的Terminal中进入Podfile所在目录,执行以下命令安装依赖:
pod install --repo-update首先,创建EMASCurl配置并安装到您的NSURLSessionConfiguration。
// 实现自定义DNS解析器(例如使用HTTPDNS)
@interface MyDNSResolver : NSObject <EMASCurlProtocolDNSResolver>
@end
@implementation MyDNSResolver
+ (NSString *)resolveDomain:(NSString *)domain {
// 这里可以接入HTTPDNS或其他DNS服务
// 返回解析后的IP地址,多个IP用逗号分隔
return @"192.168.1.100,192.168.1.101";
}
@end
// 创建EMASCurl配置
EMASCurlConfiguration *curlConfig = [EMASCurlConfiguration defaultConfiguration];
curlConfig.dnsResolver = [MyDNSResolver class]; // 设置DNS解析器
curlConfig.connectTimeoutInterval = 3.0; // 3秒连接超时
// 创建并配置NSURLSession
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
[EMASCurlProtocol installIntoSessionConfiguration:sessionConfig withConfiguration:curlConfig];之后,EMASCurl可以拦截此NSURLSessionConfiguration创建的NSURLSession发起的请求,并使用自定义的DNS解析器。
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:nil
delegateQueue:[NSOperationQueue mainQueue]];
NSURL *url = [NSURL URLWithString:@"https://httpbin.org/anything"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"请求失败,错误信息: %@", error.localizedDescription);
return;
}
NSLog(@"响应: %@", response);
NSString *body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"响应体: %@", body);
}];
[dataTask resume];本章节介绍如何使用本仓库本地构建EMASCurl xcframework。
构建过程中需要使用git克隆代码、使用automake、autoconf、libtool、pkg-config等构建工具、使用gem、ruby、xcodeproj等工具,请您确认这些命令行工具已经安装在本机。如果尚未安装,请参考以下安装命令:
brew install automake autoconf libtool pkg-config
brew install ruby
gem install xcodeproj本仓库以submodule的形式管理依赖的仓库,在克隆后需要手动拉取子模块。
git submodule update --init --recursive --progress所依赖的子模块版本信息如下:
| 依赖仓库 | 版本 |
|---|---|
| curl | curl-8_10_1 |
| nghttp2 | v1.64.0 |
./build_libcurl_xcframework.sh运行完脚本后,在out文件夹下会生成libcurl-HTTP2.xcframework。
pod install --repo-update
./build_emascurl_xcframework.sh运行完脚本后,在Build/http2/emascurl文件夹下会生成EMASCurl.xcframework,本框架目前支持HTTP1、HTTP2。
本章节介绍如何将EMASCurl添加到您的应用中。
我们提供了CocoaPods引入依赖和本地手动集成两种方式,推荐工程使用CocoaPods管理依赖。
EMASCurl和其他EMAS产品的iOS SDK,都是发布到阿里云EMAS官方维护的GitHub仓库中,因此,您需要在您的Podfile文件中包含该仓库地址。
source 'https://github.com/aliyun/aliyun-specs.git'为您需要依赖EMASCurl的target添加如下依赖。
use_framework!
pod 'EMASCurl', 'x.x.x'在您的Terminal中进入Podfile所在目录,执行以下命令安装依赖。
pod install --repo-update您需要首先按照EMASCurl构建的步骤在本地构建出EMASCurl.xcframework,然后在Xcode工程项目中(Build Phases -> Link Binary With Libraries)添加对于EMASCurl.xcframework的依赖。
EMASCurl会使用zlib进行HTTP压缩与解压,因此您需要为应用的TARGETS -> Build Settings -> Linking -> Other Linker Flags添加上-lz与-ObjC。
如果您使用自签名证书,还需将CA证书文件路径设置到EMASCurl中,具体请参考使用EMASCurl章节中的相关内容。
目前EMASCurl有两种开启方式,第一种方式是拦截指定NSURLSessionConfiguration创建的NSURLSession发起的请求,第二种方式是拦截全局的sharedSession发起的请求。
+ (void)installIntoSessionConfiguration:(nonnull NSURLSessionConfiguration *)sessionConfiguration
withConfiguration:(nonnull EMASCurlConfiguration *)configuration;首先,创建EMASCurl配置并安装到您的NSURLSessionConfiguration。
// 创建自定义配置
EMASCurlConfiguration *curlConfig = [EMASCurlConfiguration defaultConfiguration];
curlConfig.connectTimeoutInterval = 3.0;
curlConfig.enableBuiltInGzip = YES;
curlConfig.enableBuiltInRedirection = YES;
curlConfig.cacheEnabled = YES;
// 安装到session配置
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
[EMASCurlProtocol installIntoSessionConfiguration:sessionConfig withConfiguration:curlConfig];之后,EMASCurl可以拦截此NSURLSessionConfiguration创建的NSURLSession发起的请求。
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:nil
delegateQueue:[NSOperationQueue mainQueue]];
NSURL *url = [NSURL URLWithString:@"https://httpbin.org/anything"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"请求失败,错误信息: %@", error.localizedDescription);
return;
}
NSLog(@"响应: %@", response);
NSString *body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"响应体: %@", body);
}];
[dataTask resume];+ (void)registerCurlProtocol;首先,开启对于sharedSession的拦截。
[EMASCurlProtocol registerCurlProtocol];之后,EMASCurl可以拦截sharedSession发起的请求。
NSURLSession *session = [NSURLSession sharedSession];
NSURL *url = [NSURL URLWithString:@"https://httpbin.org/anything"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"请求失败,错误信息: %@", error.localizedDescription);
return;
}
NSLog(@"响应: %@", response);
NSString *body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"响应体: %@", body);
}];
[dataTask resume];假如您想取消对于sharedSession的拦截,可以调用以下API:
+ (void)unregisterCurlProtocol;EMASCurl开放了便捷的DNS hook接口,便于与HTTPDNS配合使用。只需要实现以下的DNS接口:
@protocol EMASCurlProtocolDNSResolver <NSObject>
+ (nullable NSString *)resolveDomain:(nonnull NSString *)domain;
@end例如:
@interface MyDNSResolver : NSObject <EMASCurlProtocolDNSResolver>
@end
@implementation MyDNSResolver
+ (nullable NSString *)resolveDomain:(nonnull NSString *)domain {
HttpDnsService *httpdns = [HttpDnsService sharedInstance];
HttpdnsResult* result = [httpdns resolveHostSyncNonBlocking:domain byIpType:HttpdnsQueryIPTypeBoth];
NSLog(@"httpdns resolve result: %@", result);
if (result) {
if(result.hasIpv4Address || result.hasIpv6Address) {
NSMutableArray<NSString *> *allIPs = [NSMutableArray array];
if (result.hasIpv4Address) {
[allIPs addObjectsFromArray:result.ips];
}
if (result.hasIpv6Address) {
[allIPs addObjectsFromArray:result.ipv6s];
}
NSString *combinedIPs = [allIPs componentsJoinedByString:@","];
return combinedIPs;
}
}
return nil;
}
@end然后在EMASCurl配置中设置DNS解析器:
// 创建配置并设置DNS解析器
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
config.dnsResolver = [MyDNSResolver class];
// 应用配置到session
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
[EMASCurlProtocol installIntoSessionConfiguration:sessionConfig withConfiguration:config];EMASCurl默认使用HTTP2版本。您可以在配置中指定HTTP版本:
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
// 默认已经是HTTP2,无需设置
// 如需使用HTTP/1.1:
config.httpVersion = HTTP1; // 使用HTTP/1.1HTTP1: 使用HTTP1.1 HTTP2: 首先尝试使用HTTP2,如果与服务器的HTTP2协商失败,则会退回到HTTP1.1
如果您的服务器使用自签名证书,您需要在配置中设置CA证书文件的路径,以确保EMASCurl能够正确验证SSL/TLS连接。
例如:
NSString *caFilePath = [[NSBundle mainBundle] pathForResource:@"my_ca" ofType:@"pem"];
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
config.caFilePath = caFilePath;EMASCurl默认开启内部Cookie存储功能,但只支持到[RFC 6265]标准。Cookie存储目前仍然是全局设置:
// 全局启用或禁用Cookie存储
[EMASCurlProtocol setBuiltInCookieStorageEnabled:NO]; // 禁用
[EMASCurlProtocol setBuiltInCookieStorageEnabled:YES]; // 启用(默认)如果您选择关闭内置Cookie存储,在依赖cookie能力时,需要自行处理请求/响应中的cookie字段。
NSURLSession未提供设置连接超时的方式,因此EMASCurl单独提供了此功能。您可以在配置中设置连接超时时间:
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
config.connectTimeoutInterval = 3.0; // 设置连接超时为3秒(默认为2.5秒)
// 应用配置
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
[EMASCurlProtocol installIntoSessionConfiguration:sessionConfig withConfiguration:config];对于请求的整体超时时间,请直接配置NSURLRequest中的timeoutInterval进行设置,默认是60s。
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.timeoutInterval = 20; // 设置整体超时时间为20秒typedef void(^EMASCurlUploadProgressUpdateBlock)(NSURLRequest * _Nonnull request,
int64_t bytesSent,
int64_t totalBytesSent,
int64_t totalBytesExpectedToSend);
+ (void)setUploadProgressUpdateBlockForRequest:(nonnull NSMutableURLRequest *)request uploadProgressUpdateBlock:(nonnull EMASCurlUploadProgressUpdateBlock)uploadProgressUpdateBlock;由于NSURLProtocol并未提供合适的机制来提供上传进度的跟踪,EMASCurl提供了一个额外的上传进度处理方式。您可以为每个请求设置上传进度回调。
例如:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[EMASCurlProtocol setUploadProgressUpdateBlockForRequest:request uploadProgressUpdateBlock:^(NSURLRequest * _Nonnull request, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
NSLog(@"上传进度: 已发送 %lld / 总共 %lld 字节", totalBytesSent, totalBytesExpectedToSend);
}];EMASCurl提供基本等价于URLSessionTaskTransactionMetrics的完整性能指标:
/// 综合性能指标数据结构
@interface EMASCurlTransactionMetrics : NSObject
// 时间戳信息
@property (nonatomic, strong, nullable) NSDate *fetchStartDate;
@property (nonatomic, strong, nullable) NSDate *domainLookupStartDate;
@property (nonatomic, strong, nullable) NSDate *domainLookupEndDate;
@property (nonatomic, strong, nullable) NSDate *connectStartDate;
@property (nonatomic, strong, nullable) NSDate *secureConnectionStartDate;
@property (nonatomic, strong, nullable) NSDate *secureConnectionEndDate;
@property (nonatomic, strong, nullable) NSDate *connectEndDate;
@property (nonatomic, strong, nullable) NSDate *requestStartDate;
@property (nonatomic, strong, nullable) NSDate *requestEndDate;
@property (nonatomic, strong, nullable) NSDate *responseStartDate;
@property (nonatomic, strong, nullable) NSDate *responseEndDate;
// 网络信息
@property (nonatomic, copy, nullable) NSString *networkProtocolName;
@property (nonatomic, assign) BOOL proxyConnection;
@property (nonatomic, assign) BOOL reusedConnection;
@property (nonatomic, assign) NSInteger requestHeaderBytesSent;
@property (nonatomic, assign) NSInteger responseHeaderBytesReceived;
@property (nonatomic, copy, nullable) NSString *localAddress;
@property (nonatomic, assign) NSInteger localPort;
@property (nonatomic, copy, nullable) NSString *remoteAddress;
@property (nonatomic, assign) NSInteger remotePort;
// SSL/TLS信息(暂不支持,留空)
@property (nonatomic, copy, nullable) NSString *tlsProtocolVersion;
@property (nonatomic, copy, nullable) NSString *tlsCipherSuite;
@end使用综合性能指标回调示例:
// 创建配置并设置性能指标回调
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
config.transactionMetricsObserver = ^(NSURLRequest * _Nonnull request, BOOL success, NSError * _Nullable error, EMASCurlTransactionMetrics * _Nonnull metrics) {
if (!success) {
NSLog(@"请求失败,URL: %@, 错误: %@", request.URL.absoluteString, error.localizedDescription);
return;
}
NSLog(@"综合性能指标 [%@]:", request.URL.absoluteString);
NSLog(@"获取开始时间: %@", metrics.fetchStartDate);
NSLog(@"域名解析: %@ - %@", metrics.domainLookupStartDate, metrics.domainLookupEndDate);
NSLog(@"连接建立: %@ - %@", metrics.connectStartDate, metrics.connectEndDate);
NSLog(@"安全连接: %@ - %@", metrics.secureConnectionStartDate, metrics.secureConnectionEndDate);
NSLog(@"请求处理: %@ - %@", metrics.requestStartDate, metrics.requestEndDate);
NSLog(@"响应接收: %@ - %@", metrics.responseStartDate, metrics.responseEndDate);
NSLog(@"协议: %@", metrics.networkProtocolName);
NSLog(@"连接重用: %@", metrics.reusedConnection ? @"是" : @"否");
NSLog(@"请求头字节: %ld, 响应头字节: %ld", (long)metrics.requestHeaderBytesSent, (long)metrics.responseHeaderBytesReceived);
NSLog(@"地址: %@:%ld -> %@:%ld", metrics.localAddress, (long)metrics.localPort, metrics.remoteAddress, (long)metrics.remotePort);
NSLog(@"TLS: %@ (%@)", metrics.tlsProtocolVersion, metrics.tlsCipherSuite);
};
// 应用配置到session
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
[EMASCurlProtocol installIntoSessionConfiguration:sessionConfig withConfiguration:config];EMASCurl提供了多级别的日志系统,支持组件化的日志记录,便于调试和问题排查。
+ (void)setLogLevel:(EMASCurlLogLevel)logLevel;
+ (EMASCurlLogLevel)currentLogLevel;EMASCurl支持以下日志级别:
EMASCurlLogLevelOff(0): 禁用所有日志EMASCurlLogLevelError(1): 仅显示错误信息EMASCurlLogLevelInfo(2): 显示信息和错误级别日志EMASCurlLogLevelDebug(3): 显示所有日志,包括详细的调试信息和libcurl输出
例如:
// 设置为信息级别,显示错误和信息日志
[EMASCurlProtocol setLogLevel:EMASCurlLogLevelInfo];
// 获取当前日志级别
EMASCurlLogLevel currentLevel = [EMASCurlProtocol currentLogLevel];
NSLog(@"当前日志级别: %ld", (long)currentLevel);
// 设置为调试级别,显示所有日志
[EMASCurlProtocol setLogLevel:EMASCurlLogLevelDebug];EMASCurl使用组件化的日志记录,每个日志消息都会标明来源组件,便于问题定位:
[EC-Protocol]: 主协议处理相关[EC-Request]: 请求验证和过滤相关[EC-DNS]: DNS解析相关[EC-SSL]: SSL/TLS配置相关[EC-Cache]: 响应缓存相关[EC-Response]: 响应处理相关[EC-Performance]: 性能指标相关[EC-Proxy]: 代理配置相关[EC-libcurl]: libcurl详细输出[EC-Manager]: 连接管理相关
日志输出格式:
[时间戳] [级别] [组件] 消息内容
示例输出:
[2024-12-27 10:30:15.123] [INFO] [EC-Protocol] Starting request for URL: https://example.com
[2024-12-27 10:30:15.124] [DEBUG] [EC-DNS] Resolved example.com to IPs: 93.184.216.34
[2024-12-27 10:30:15.125] [INFO] [EC-SSL] Certificate validation enabled
[2024-12-27 10:30:15.200] [INFO] [EC-Manager] Transfer completed successfully for URL: https://example.com (HTTP 200)
EMASCurl支持将日志输出到自定义组件,而不仅限于控制台。您可以通过设置自定义日志处理器,将日志重定向到文件、远程服务器或自定义UI界面。
+ (void)setLogHandler:(nullable EMASCurlLogHandlerBlock)handler;日志处理器Block定义:
typedef void(^EMASCurlLogHandlerBlock)(EMASCurlLogLevel level, NSString *component, NSString *message);使用示例:
// 示例1:将日志写入文件
[EMASCurlProtocol setLogHandler:^(EMASCurlLogLevel level, NSString *component, NSString *message) {
// 构建日志字符串
NSString *levelString = @"INFO";
if (level == EMASCurlLogLevelError) {
levelString = @"ERROR";
} else if (level == EMASCurlLogLevelDebug) {
levelString = @"DEBUG";
}
NSString *logEntry = [NSString stringWithFormat:@"[%@] [%@] %@\n", levelString, component, message];
// 写入日志文件
[MyLogger appendToFile:logEntry];
}];
// 示例2:发送到远程日志服务
[EMASCurlProtocol setLogHandler:^(EMASCurlLogLevel level, NSString *component, NSString *message) {
if (level == EMASCurlLogLevelError) {
[MyAnalytics reportError:message component:component];
}
}];
// 示例3:显示在自定义UI
[EMASCurlProtocol setLogHandler:^(EMASCurlLogLevel level, NSString *component, NSString *message) {
dispatch_async(dispatch_get_main_queue(), ^{
[MyDebugConsole appendLog:message level:level component:component];
});
}];
// 恢复默认NSLog行为
[EMASCurlProtocol setLogHandler:nil];注意事项:
- 如果不设置自定义处理器,日志将默认输出到控制台(NSLog),保持向后兼容
- 日志处理器会在日志产生的线程上调用,如需UI操作请切换到主线程
- 传入
nil可恢复默认 NSLog 行为 - 自定义处理器会接收到原始的日志级别、组件名称和消息内容,您可以自由格式化输出
EMASCurl允许您设置域名白名单和黑名单来控制哪些请求会被拦截处理:
- 处理请求时,EMASCurl会先检查黑名单,再检查白名单
- 白名单:只拦截白名单中的域名请求
- 黑名单:不拦截黑名单中的域名请求
例如:
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
// 只拦截这些域名的请求
config.domainWhiteList = @[@"example.com", @"api.example.com"];
// 不拦截这些域名的请求
config.domainBlackList = @[@"analytics.example.com"];
// 应用配置
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
[EMASCurlProtocol installIntoSessionConfiguration:sessionConfig withConfiguration:config];EMASCurl默认开启内部Gzip压缩。开启后,请求的header中会自动添加Accept-Encoding: deflate, gzip,并自动解压响应内容。若关闭,则需要自行处理请求/响应中的gzip字段。
例如:
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
config.enableBuiltInGzip = NO; // 关闭内置Gzip支持
// 或
config.enableBuiltInGzip = YES; // 启用(默认)EMASCurl可以配置是否自动处理HTTP重定向(如301、302等状态码)。
例如:
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
config.enableBuiltInRedirection = YES; // 开启内部重定向支持(默认)
// 或
config.enableBuiltInRedirection = NO; // 关闭设置用于公钥固定(Public Key Pinning)的公钥文件路径。libcurl 会使用此文件中的公钥信息来验证服务器证书链中的公钥。
要求公钥 PEM 文件的结构:
- 公钥 PEM 文件必须包含一个有效的公钥信息,格式为 PEM 格式,即包含
-----BEGIN PUBLIC KEY-----和-----END PUBLIC KEY-----区块,内容为公钥的 base64 编码。 - 文件内容示例:
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A... ...base64 data... -----END PUBLIC KEY-----
如果用户仅持有 PEM 格式的证书文件,而不是单独的公钥 PEM 文件,可以通过以下命令从证书中提取公钥:
使用 OpenSSL 工具:
openssl x509 -in your-cert.pem -pubkey -noout -out publickey.pem该命令会从 PEM 证书文件(your-cert.pem)中提取公钥,并将公钥保存到 publickey.pem 文件中。生成的公钥文件应符合上述结构要求,可以直接用于公钥固定。
例如:
NSString *publicKeyPath = [[NSBundle mainBundle] pathForResource:@"my_public_key" ofType:@"pem"];
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
config.publicKeyPinningKeyPath = publicKeyPath;设置是否开启 SSL 证书校验。默认情况下,证书校验是开启的 (YES)。
- 当设置为
YES时,libcurl 会验证服务器证书的有效性,包括证书链、有效期等。 - 当设置为
NO时,libcurl 将不执行证书校验,这通常仅用于测试或连接到使用自签名证书且无法提供 CA 证书的服务器。在生产环境中关闭证书校验会带来安全风险,请谨慎使用。
例如:
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
config.certificateValidationEnabled = NO; // 关闭证书校验
// 或
config.certificateValidationEnabled = YES; // 开启证书校验 (默认行为)设置是否开启 SSL 证书中的域名校验。默认情况下,域名校验是开启的 (YES)。
- 当设置为
YES时,libcurl 会验证服务器证书中的 Common Name (CN) 或 Subject Alternative Name (SAN) 是否与请求的域名匹配。 - 当设置为
NO时,libcurl 将不执行域名校验。在生产环境中关闭域名校验会带来安全风险,可能导致中间人攻击,请谨慎使用。
例如:
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
config.domainNameVerificationEnabled = NO; // 关闭域名校验
// 或
config.domainNameVerificationEnabled = YES; // 开启域名校验 (默认行为)设置手动代理服务器。设置后会覆盖系统代理设置。
代理字符串格式:[protocol://]user:password@host[:port]
例如: http://user:[email protected]:8080 或 socks5://127.0.0.1:1080
例如:
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
// 设置HTTP代理(当 proxyServer 非空时,总是使用该代理)
config.proxyServer = @"http://user:[email protected]:8080";
// 或设置SOCKS5代理
// config.proxyServer = @"socks5://192.168.1.100:1080";
// 清空以回退到系统代理
// config.proxyServer = nil;设置是否启用HTTP缓存。EMASCurl默认启用HTTP缓存。
缓存功能特性包括:
- 自动缓存可缓存的HTTP响应
- 支持304 Not Modified响应处理
- 遵循Cache-Control头信息控制缓存行为
- 自动管理和清理过期缓存
- 缓存使用
[NSURLCache sharedURLCache]
例如:
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
config.cacheEnabled = YES; // 启用HTTP缓存(默认)
// 或
config.cacheEnabled = NO; // 禁用HTTP缓存EMASCurlConfiguration 提供了所有网络配置选项的集中管理。以下是完整的属性列表:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| 核心网络设置 | |||
httpVersion |
HTTPVersion | HTTP2 | HTTP协议版本(HTTP1/HTTP2) |
connectTimeoutInterval |
NSTimeInterval | 2.5 | 连接超时时间(秒) |
enableBuiltInGzip |
BOOL | YES | 是否启用内置gzip压缩 |
enableBuiltInRedirection |
BOOL | YES | 是否启用内置重定向处理 |
| DNS和代理 | |||
dnsResolver |
Class | nil | 自定义DNS解析器类 |
proxyServer |
NSString | nil | 代理服务器URL(非空时总是使用该代理) |
| 安全设置 | |||
caFilePath |
NSString | nil | CA证书文件路径 |
publicKeyPinningKeyPath |
NSString | nil | 公钥固定文件路径 |
certificateValidationEnabled |
BOOL | YES | 是否启用证书验证 |
domainNameVerificationEnabled |
BOOL | YES | 是否启用域名验证 |
| 域名过滤 | |||
domainWhiteList |
NSArray | nil | 域名白名单 |
domainBlackList |
NSArray | nil | 域名黑名单 |
| 缓存 | |||
cacheEnabled |
BOOL | YES | 是否启用HTTP缓存 |
| 性能监控 | |||
transactionMetricsObserver |
Block | nil | 性能指标回调块 |
使用示例:
// 创建完整配置
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
// 网络设置
config.connectTimeoutInterval = 5.0;
config.enableBuiltInGzip = YES;
config.enableBuiltInRedirection = YES;
// DNS解析
config.dnsResolver = [MyDNSResolver class];
// 安全设置
config.certificateValidationEnabled = YES;
config.domainNameVerificationEnabled = YES;
// 域名过滤
config.domainWhiteList = @[@"api.example.com"];
config.domainBlackList = @[@"tracking.example.com"];
// 缓存
config.cacheEnabled = YES;
// 性能监控
config.transactionMetricsObserver = ^(NSURLRequest *request, BOOL success,
NSError *error, EMASCurlTransactionMetrics *metrics) {
// 处理性能指标
};
// 应用配置
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
[EMASCurlProtocol installIntoSessionConfiguration:sessionConfig withConfiguration:config];- iOS 17 及以上:全场景支持
- iOS 17 以下:WKWebView 不支持代理
- iOS 17 以下:NSURLSession 仅 HTTPS 走代理,HTTP 明文请求不走代理
EMASLocalProxy 是阿里云 EMAS 团队提供的本地 HTTPS 代理服务,为 iOS 17+ 的 WKWebView 提供统一代理与 HTTPDNS 能力。本 README 仅保留 WKWebView 相关指引;NSURLSession 场景推荐使用 EMASCurl。
- WKWebView 代理:在 iOS 17.0+ 通过 proxyConfigurations 支持 WKWebView
- HTTPDNS域名解析:无缝集成阿里云 HTTPDNS 服务
- 现代API支持:使用 iOS 17.0+ proxyConfigurations API
- 简化配置:无需复杂的接入配置
- 生产级稳定性:基于 Network framework 的可靠实现
在您的 Podfile 文件中添加 EMASLocalProxy 依赖:
source 'https://github.com/aliyun/aliyun-specs.git'
target 'yourAppTarget' do
use_framework!
pod 'EMASLocalProxy', 'x.x.x'
end当前最新版本: 1.4.2
仅在 iOS 17.0+ 支持通过 proxyConfigurations 配置代理;低于 iOS 17 不支持 WKWebView 代理。
WKWebView 的代理配置相对简单,因为 WebView 通常不会在应用启动时立即加载:
#import <EMASLocalProxy/EMASLocalHttpProxy.h>
#import <WebKit/WebKit.h>
- (void)setupWebViewWithProxy {
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
// EMASLocalProxy 会内部检查系统版本,iOS 17 以下会返回 NO
BOOL success = [EMASLocalHttpProxy installIntoWebViewConfiguration:config];
NSLog(@"WebView代理配置: %@", success ? @"成功" : @"失败,使用系统网络");
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
[self.view addSubview:self.webView];
// 加载网页
NSURL *url = [NSURL URLWithString:@"https://example.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}EMASLocalProxy 可以与阿里云 HTTPDNS 服务无缝集成,提供自定义域名解析能力:
#import <AlicloudHttpDNS/AlicloudHttpDNS.h>
// 配置DNS解析器
[EMASLocalHttpProxy setDNSResolverBlock:^NSArray<NSString *> * _Nullable(NSString * _Nonnull hostname) {
HttpDnsService *httpdns = [HttpDnsService sharedInstance];
HttpdnsResult *result = [httpdns resolveHostSyncNonBlocking:hostname byIpType:HttpdnsQueryIPTypeBoth];
if (result && (result.hasIpv4Address || result.hasIpv6Address)) {
NSMutableArray<NSString *> *allIPs = [NSMutableArray array];
if (result.hasIpv4Address) {
[allIPs addObjectsFromArray:result.ips];
}
if (result.hasIpv6Address) {
[allIPs addObjectsFromArray:result.ipv6s];
}
NSLog(@"HTTPDNS解析成功,域名: %@, IP: %@", hostname, allIPs);
return allIPs;
}
NSLog(@"HTTPDNS解析失败,域名: %@", hostname);
return nil;
}];EMASLocalProxy 提供了完善的日志系统,便于开发和调试:
// 设置日志级别
typedef NS_ENUM(NSInteger, EMASLocalHttpProxyLogLevel) {
EMASLocalHttpProxyLogLevelNone = 0, // 关闭日志
EMASLocalHttpProxyLogLevelError = 1, // 仅错误日志
EMASLocalHttpProxyLogLevelInfo = 2, // 信息和错误日志
EMASLocalHttpProxyLogLevelDebug = 3 // 所有日志(包括详细调试信息)
};
// 开启调试日志
[EMASLocalHttpProxy setLogLevel:EMASLocalHttpProxyLogLevelDebug];EMASLocalProxy支持将日志输出到自定义组件,例如文件、远程服务器或自定义UI界面:
// 设置自定义日志处理器
[EMASLocalHttpProxy setLogHandler:^(EMASLocalHttpProxyLogLevel level, NSString *component, NSString *message) {
// 自定义处理逻辑
NSString *levelString = @"INFO";
if (level == EMASLocalHttpProxyLogLevelError) {
levelString = @"ERROR";
} else if (level == EMASLocalHttpProxyLogLevelDebug) {
levelString = @"DEBUG";
}
NSString *logEntry = [NSString stringWithFormat:@"[%@] [%@] %@", levelString, component, message];
// 写入文件或发送到远程服务
[MyLogger writeLog:logEntry];
}];
// 恢复默认NSLog行为
[EMASLocalHttpProxy setLogHandler:nil];注意事项:
- 如果不设置自定义处理器,日志将默认输出到控制台(NSLog)
- 日志处理器会在日志产生的线程上调用,如需UI操作请切换到主线程
- 传入
nil可恢复默认 NSLog 行为
- Apache 2.0
- 阿里云HTTPDNS官方文档中心
- 阿里云EMAS开发交流钉钉群:35248489