YMHTTP 1.0.0

YMHTTP 1.0.0

zymxxxs 维护。



YMHTTP 1.0.0

  • 作者
  • zymxxxs

YMHTTP

Build Status Version License Platform

YMHTTP 是一个适用于 iOS 平台的基于 libcurl 的 IO 多路复用 HTTP 框架,它的 API 设计和行为与 NSURLSession 保持高度一致。

由于 YMHTTP 是基于 libcurl 封装的,因此具有很高的定制性,目前的版本保持与 NSURLSession 在 API 上一致的同时,还扩展了 DNS 的功能(包括 SNI 场景)。

如果您对 DNS 相关的问题比较感兴趣,可以查看这篇文章 文章,它汇总了在 iOS 上支持 DNS 需要面对的一些技术难点、相关解决方案以及 YMHTTP 诞生的初衷。

说明

  1. 您可以通过 NSURLSession 来查阅具体细节。
  2. 这里有一份非常不错的NSURLSession最全攻略,可以查漏补缺(来自搜狐技术产品)。
  3. YMHTTP 和 NSURLSession 非常相似,一个是 YM 前缀,一个是 NS 前缀,对外提供的 API 相互一致
  4. 如果您已经非常了解 NSURLSession,那么可以直接查阅 Connect to specific host and port 部分来获取 DNS 相关内容
  5. 不支持 System Background Task 相关功能,这个真的无能为力

安装

目前 YMHTTPUT覆盖率 大约在 80%,覆盖了各个 case,目前已经发布了 beta 版本,欢迎大家测试反馈~。

pod 'YMHTTP', '1.0.0-beta.1'

要求

  • iOS 10.0
  • Xcode 11.3.1
  • libcurl 7.64.1 + SecureTransport

使用

0x01 YMSession

// 创建 sharedSession
YMURLSession *sharedSession = [YMURLSession sharedSession];

// 使用指定配置创建会话
YMURLSessionConfiguration *config = [YMURLSessionConfiguration defaultSessionConfiguration];
YMURLSession *sessionNoDelegate = [YMURLSession sessionWithConfiguration:config];

// 创建具有指定会话配置,委托和操作队列的会话
YMURLSession *session = [YMURLSession sessionWithConfiguration:config
                                                      delegate:self
                                                 delegateQueue:nil];

0x02 将数据任务添加到会话

- (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request;
- (YMURLSessionTask *)taskWithURL:(NSURL *)url;

通过指定URL或请求来创建任务

- (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request
                    completionHandler:(void (^)(NSData *_Nullable data,
                                                NSHTTPURLResponse *_Nullable response,
                                                NSError *_Nullable error))completionHandler;

- (YMURLSessionTask *)taskWithURL:(NSURL *)url
                completionHandler:(void (^)(NSData *_Nullable data,
                                            NSHTTPURLResponse *_Nullable response,
                                            NSError *_Nullable error))completionHandler;

通过指定URL或请求来创建任务,任务完成后调用completionHandler

示例

  1. 代理方式
// create
YMURLSessionTask *task = [session taskWithURL:[NSURL URLWithString:@"http://httpbin.org/get"]];
[task resume];

// delegate
- (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didCompleteWithError:(NSError *)error {

}

- (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didReceiveData:(NSData *)data {

}

- (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse * _Nullable))completionHandler {
    completionHandler(proposedResponse);
}

-(void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(YMURLSessionResponseDisposition))completionHandler {
    completionHandler(YMURLSessionResponseAllow);
}
  1. completionHandler方式
YMURLSessionTask *task = [session taskWithURL:[NSURL URLWithString:@"http://httpbin.org/get"] completionHandler:^(NSData * _Nullable data, NSHTTPURLResponse * _Nullable response, NSError * _Nullable error) {
    
}];
[task resume];

0x03 将上传任务添加到会话

- (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;

- (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;

- (YMURLSessionTask *)taskWithStreamedRequest:(NSURLRequest *)request;

通过指定请求创建一个上传任务。

taskWithStreamedRequest 方法会调用 YMURLSession:task:needNewBodyStream: 代理方法,您需要通过 completionHandler 返回一个 NSInputStream 对象。当然,您也可以使用 NSURLMutableRequest 创建对象,并在 bodyStream 传入 NSInputStream 对象。

如果您需要上传大文件,建议使用 fromFile 方法,虽然 taskWithStreamedRequest 也支持大文件的传输,但其形式为循环执行 读取指定长度内容 -> 上传该内容,该行为在内部线程是同步的,而 fromFile 方式会每次异步获取 3 * CURL_MAX_WRITE_SIZE 长度的内容供 libcurl 上传(CURL_MAX_WRITE_SIZE 为单次支持的最大上传长度),不仅减少文件 I/O 次数,也减少同步阻塞的时间,优化上传效率。

- (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request
                             fromFile:(NSURL *)fileURL
                    completionHandler:(void (^)(NSData *_Nullable data,
                                                NSHTTPURLResponse *_Nullable response,
                                                NSError *_Nullable error))completionHandler;

- (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request
                             fromData:(nullable NSData *)bodyData
                    completionHandler:(void (^)(NSData *_Nullable data,
                                                NSHTTPURLResponse *_Nullable response,
                                                NSError *_Nullable error))completionHandler;

通过指定请求创建任务,任务完成后调用 completionHandler

0x04 将下载任务添加到会话

- (YMURLSessionTask *)taskWithDownloadRequest:(NSURLRequest *)request;

- (YMURLSessionTask *)taskWithDownloadURL:(NSURL *)url;

通过指定请求创建一个下载任务,并返回临时文件,由于该文件是临时文件,因此必须打开该文件进行读取,或将其移动到应用程序沙盒容器目录中的永久位置,支持大文件下载。

当然,您也可以使用 taskWithRequesttaskWithURL 来自定义下载任务。

- (YMURLSessionTask *)taskWithDownloadRequest:(NSURLRequest *)request
                            completionHandler:(void (^)(NSURL *_Nullable location,
                                                        NSHTTPURLResponse *_Nullable response,
                                                        NSError *_Nullable error))completionHandler;

- (YMURLSessionTask *)taskWithDownloadURL:(NSURL *)url
                        completionHandler:(void (^)(NSURL *_Nullable location,
                                                    NSHTTPURLResponse *_Nullable response,
                                                    NSError *_Nullable error))completionHandler;

0x05 连接到特定主机和端口

- (YMURLSessionTask *)taskWithURL:(NSURL *)url connectToHost:(NSString *)host;

- (YMURLSessionTask *)taskWithURL:(NSURL *)url connectToHost:(NSString *)host connectToPort:(NSInteger)port;

// 创建包含 host port 的 request
[[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://httpbin.org/get"] connectToHost:@"52.202.2.199"];
[[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://httpbin.org/get"] connectToHost:@"52.202.2.199" connectToPort:443];
[[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://httpbin.org/get"]
                           connectToHost:@"52.202.2.199"
                           connectToPort:443
                             cachePolicy:NSURLRequestUseProtocolCachePolicy
                         timeoutInterval:60];

连接到特定的主机和端口,其中主机支持IP地址的形式。如果使用正常的域名+主机+端口的请求方式,那么框架内部可以自动处理Cookie、Cache以及302等问题,当然该接口也支持SNI场景。

备注:该接口不会影响到DNS缓存,更多信息可以参考这里 https://curl.haxx.se/libcurl/c/CURLOPT_CONNECT_TO.html。

libcurl

libcurl是免费的,线程安全的,IPv6兼容的,功能丰富,支持良好,速度快,有详尽的文档,并被许多知名的大型和成功的公司使用。

libcurl是免费的,线程安全的,IPv6兼容的,功能丰富,支持良好,速度快,有完整的文档记录,已经被许多知名的,大的和成功的公司使用。

您可以在此查看更多内容:https://curl.haxx.se/

libcurl 版本

当前使用libcurl 7.64.1版本,与macOS Catalina保持一致,使用curl-android-ios进行构建,你也可以选择喜欢的版本进行构建。

HTTP/2

目前版本不支持HTTP/2,你可以使用Build-OpenSSL-cURL来构建支持HTTP/2功能的版本。

注意:在Build-OpenSSL-cURL中使用的是OpenSSL,而目前macOS Catalina中则是使用LibreSSL。我在Build-OpenSSL-cURL的基础上修改了一个支持LibreSSL的版本,链接地址:https://github.com/zymxxxs/libcurl-nghttp2-libressl-ios。

备注:

  • 支持HTTP/2需要考虑包大小的影响
  • 目前第一阶段的工作主要是在外接口与NSURLSession对齐以及支持DNS,HTTP/2暂时不在支持范围内,所以上述脚本只能保证构建出静态库,暂未做过较多的验证,请知悉。

最后

如果你研究过swift-corelibs-foundation的相关源码,你会发现其NSURLSession系列的功能(HTTP、FTP)是基于libcurl进行封装的。如果你想学习它,建议你直接学习官方源码,YMHTTP也是参考了其中大量的实现,然后补充了一些尚未实现的功能以及修复了一些BUG。

YMHTTP的诞生初衷是为了彻底解决HTTP DNS的问题(性能+SNI场景+ Cache + Cookies + 302),现在回头来看,倒像是切开了一道口,获得了更多的自由度,如果您需要什么功能,请通过ISSUE告诉我。

如何贡献

非常欢迎你的加入!你可以在 提出问题 或提交一个 Pull Request。

许可协议

YMHTTP 采用 MIT 许可协议。更多详情请参阅 LICENSE 文件。

感谢

待办事项

  • 目前指定 IP 的功能是通过 CURLOPT_CONNECT_TO 来实现的,其优点是不会影响 DNS Cache,但在 Charles 中会直接显示 IP 的请求。需要考虑是否替换为 CURLOPT_RESOLVE 参数,但对于 DNS Cache 的问题,是需要影响还是不能影响,需要删除吗?或者说,是使用 CURLOPT_CONNECT_TO 还是 CURLOPT_RESOLVE,哪一个更为合理?
  • 目前大多数仍然基于 AFNetworking 进行封装,需要考虑是否提供一个 YMNetworking 版本以方便接入,也可以参考 retrofit 的接口实现。
  • NSURLSessionTaskMetrics 尚未实现。使用 curl_easy_getinfo 实现,目前实现这个功能确实需要较大的代码改动,需要着重设计一下,目前属于重要不紧急,可以延后。