├── .clang-format ├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Example ├── Podfile ├── Podfile.lock ├── Tests │ ├── Tasks │ │ ├── YMCacheDataTask.h │ │ ├── YMCacheDataTask.m │ │ ├── YMDataTask.h │ │ ├── YMDataTask.m │ │ ├── YMDownloadTask.h │ │ ├── YMDownloadTask.m │ │ ├── YMHTTPRedirectionDataTask.h │ │ ├── YMHTTPRedirectionDataTask.m │ │ ├── YMHTTPUploadDelegate.h │ │ ├── YMHTTPUploadDelegate.m │ │ ├── YMSessionDelegate.h │ │ └── YMSessionDelegate.m │ ├── TestURLSession.m │ ├── TestURLSessionCache.m │ ├── TestURLSesssionDirect.m │ ├── Tests-Info.plist │ ├── Tests-Prefix.pch │ └── en.lproj │ │ └── InfoPlist.strings ├── YMHTTP.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── YMHTTP-Example.xcscheme ├── YMHTTP.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── YMHTTP │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── YMAppDelegate.h │ ├── YMAppDelegate.m │ ├── YMHTTP-Info.plist │ ├── YMHTTP-Prefix.pch │ ├── YMViewController.h │ ├── YMViewController.m │ ├── en.lproj │ └── InfoPlist.strings │ └── main.m ├── LICENSE ├── README.md ├── YMHTTP.podspec ├── YMHTTP ├── NSURLRequest+YMCategory.h ├── NSURLRequest+YMCategory.m ├── YMHTTP.h ├── YMURLSession.h ├── YMURLSession.m ├── YMURLSessionConfiguration.h ├── YMURLSessionConfiguration.m ├── YMURLSessionDelegate.h ├── YMURLSessionTask.h ├── YMURLSessionTask.m ├── core │ ├── NSArray+YMCategory.h │ ├── NSArray+YMCategory.m │ ├── NSInputStream+YMCategory.h │ ├── NSInputStream+YMCategory.m │ ├── NSURLCache+YMCategory.h │ ├── NSURLCache+YMCategory.m │ ├── YMTaskRegistry.h │ ├── YMTaskRegistry.m │ ├── YMTransferState.h │ ├── YMTransferState.m │ ├── YMURLCacheHelper.h │ ├── YMURLCacheHelper.m │ ├── YMURLSessionAuthenticationChallengeSender.h │ ├── YMURLSessionAuthenticationChallengeSender.m │ ├── YMURLSessionTaskBehaviour.h │ ├── YMURLSessionTaskBehaviour.m │ ├── YMURLSessionTaskBody.h │ ├── YMURLSessionTaskBody.m │ ├── YMURLSessionTaskBodySource.h │ └── YMURLSessionTaskBodySource.m ├── curl │ ├── YMEasyHandle.h │ ├── YMEasyHandle.m │ ├── YMMacro.h │ ├── YMMultiHandle.h │ ├── YMMultiHandle.m │ ├── YMTimeoutSource.h │ └── YMTimeoutSource.m └── libcurl │ ├── curl.h │ ├── curlver.h │ ├── easy.h │ ├── libcurl.a │ ├── mprintf.h │ ├── multi.h │ ├── stdcheaders.h │ ├── system.h │ ├── typecheck-gcc.h │ └── urlapi.h ├── _Pods.xcodeproj └── code_format.py /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | --- 4 | Language: ObjC 5 | BinPackArguments: false 6 | BinPackParameters: false 7 | ColumnLimit: 120 8 | PointerAlignment: Right 9 | IndentWidth: 4 10 | ObjCSpaceAfterProperty: true 11 | ObjCSpaceBeforeProtocolList: true 12 | ObjCBlockIndentWidth: 4 13 | TabWidth: 4 14 | UseTab: Never -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.h linguist-language=objective-c 2 | *.m linguist-language=objective-c -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | test: 11 | runs-on: macOS-latest 12 | steps: 13 | - name: print env 14 | run: | 15 | xcodebuild -version 16 | - uses: actions/checkout@master 17 | - name: install cocoapods 18 | run: gem install cocoapods 19 | - name: install dependencies 20 | run: | 21 | cd Example 22 | pod install 23 | shell: bash 24 | - name: run test 25 | run: xcodebuild test -workspace ./Example/YMHTTP.xcworkspace -scheme 'YMHTTP-Example' -destination 'platform=iOS Simulator,name=iPhone 11' 26 | shell: bash 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | # CocoaPods 34 | # 35 | # We recommend against adding the Pods directory to your .gitignore. However 36 | # you should judge for yourself, the pros and cons are mentioned at: 37 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 38 | # 39 | Pods/ 40 | # 41 | # Add this line if you want to avoid checking in source code from the Xcode workspace 42 | # *.xcworkspace 43 | 44 | # Carthage 45 | # 46 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 47 | # Carthage/Checkouts 48 | 49 | Carthage/Build/ 50 | 51 | # fastlane 52 | # 53 | # It is recommended to not store the screenshots in the git repo. 54 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 55 | # For more information about the recommended setup visit: 56 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 57 | 58 | fastlane/report.xml 59 | fastlane/Preview.html 60 | fastlane/screenshots/**/*.png 61 | fastlane/test_output 62 | 63 | # Code Injection 64 | # 65 | # After new code Injection tools there's a generated folder /iOSInjectionProject 66 | # https://github.com/johnno1962/injectionforxcode 67 | 68 | iOSInjectionProject/ 69 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/ 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/YMHTTP.xcworkspace -scheme YMHTTP-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | * feat: 重定向最大支持 16 次 3 | * build: 补充 stream 相关 UT 4 | 5 | ## 1.0.0-beta.3 6 | * fix: task 不再触发 needNewBodyStream 回调 7 | * fix: 重定向请求继续使用之前的超时时间 8 | * fix: HTTPBody length == 0 与 HTTPBody == nil 保持同等处理 9 | * fix: 对于 POST 请求设置默认 Content-Type 字段 10 | * feat: 处理 3xx 请求,willPerformHTTPRedirection 回调更加合理 11 | 12 | ## 1.0.0-beta.2 13 | * fix: GET 请求不再设置 `Content-Length` 14 | * feat: 支持 CURLOPT_XFERINFOFUNCTION,NSProgress 获取进度更加准确 15 | * fix: redirect 逻辑完善 16 | 17 | ## 1.0.0-beta.1 18 | * first dev version 19 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | platform :ios, '10.0' 4 | 5 | target 'YMHTTP_Example' do 6 | pod 'YMHTTP', :path => '../' 7 | 8 | target 'YMHTTP_Tests' do 9 | inherit! :search_paths 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - YMHTTP (1.0.0): 3 | - YMHTTP/libcurl (= 1.0.0) 4 | - YMHTTP/libcurl (1.0.0) 5 | 6 | DEPENDENCIES: 7 | - YMHTTP (from `../`) 8 | 9 | EXTERNAL SOURCES: 10 | YMHTTP: 11 | :path: "../" 12 | 13 | SPEC CHECKSUMS: 14 | YMHTTP: 4838cd90418e98a36a91f6b40be584abf9618987 15 | 16 | PODFILE CHECKSUM: 8ad634c421ecba2f8ed89cfc1b575c535293f61d 17 | 18 | COCOAPODS: 1.9.1 19 | -------------------------------------------------------------------------------- /Example/Tests/Tasks/YMCacheDataTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMCacheDataTask.h 3 | // YMHTTP_Tests 4 | // 5 | // Created by zymxxxs on 2020/3/8. 6 | // Copyright © 2020 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import "YMDataTask.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface YMCacheDataTask : YMDataTask 14 | 15 | @property (nonatomic, assign) YMURLSessionResponseDisposition disposition; 16 | @property (nonatomic, strong) NSHTTPURLResponse *response; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /Example/Tests/Tasks/YMCacheDataTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMCacheDataTask.m 3 | // YMHTTP_Tests 4 | // 5 | // Created by zymxxxs on 2020/3/8. 6 | // Copyright © 2020 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import "YMCacheDataTask.h" 10 | 11 | @implementation YMCacheDataTask 12 | 13 | - (void)YMURLSession:(YMURLSession *)session 14 | task:(YMURLSessionTask *)task 15 | didReceiveResponse:(NSHTTPURLResponse *)response 16 | completionHandler:(void (^)(YMURLSessionResponseDisposition))completionHandler { 17 | self.response = (NSHTTPURLResponse *)response; 18 | completionHandler(self.disposition); 19 | if (self.responseReceivedExpectation) { 20 | [self.responseReceivedExpectation fulfill]; 21 | } 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Example/Tests/Tasks/YMDataTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMDataTask.h 3 | // YMHTTP_Tests 4 | // 5 | // Created by zymxxxs on 2020/3/5. 6 | // Copyright © 2020 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface YMDataTask : XCTestCase 16 | 17 | @property (copy) NSDictionary *result; 18 | @property (copy) NSDictionary *args; 19 | @property (strong) XCTestExpectation *responseReceivedExpectation; 20 | @property (strong) XCTestExpectation *cancelExpectation; 21 | @property (strong) XCTestExpectation *dataTaskExpectation; 22 | @property (copy) YMURLSessionTask *task; 23 | @property BOOL error; 24 | 25 | - (instancetype)initWithExpectation:(XCTestExpectation *)expectation; 26 | 27 | - (void)runWithRequest:(NSURLRequest *)request; 28 | 29 | - (void)runWithURL:(NSURL *)URL; 30 | 31 | - (void)cancel; 32 | 33 | @end 34 | 35 | NS_ASSUME_NONNULL_END 36 | -------------------------------------------------------------------------------- /Example/Tests/Tasks/YMDataTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMDataTask.m 3 | // YMHTTP_Tests 4 | // 5 | // Created by zymxxxs on 2020/3/5. 6 | // Copyright © 2020 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import "YMDataTask.h" 10 | 11 | @implementation YMDataTask 12 | 13 | - (instancetype)initWithExpectation:(XCTestExpectation *)expectation { 14 | self = [super init]; 15 | if (self) { 16 | self.dataTaskExpectation = expectation; 17 | } 18 | return self; 19 | } 20 | 21 | - (void)runWithRequest:(NSURLRequest *)request { 22 | YMURLSessionConfiguration *config = [YMURLSessionConfiguration defaultSessionConfiguration]; 23 | config.timeoutIntervalForRequest = 8; 24 | YMURLSession *session = [YMURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; 25 | self.task = [session taskWithRequest:request]; 26 | [self.task resume]; 27 | } 28 | 29 | - (void)runWithURL:(NSURL *)URL { 30 | YMURLSessionConfiguration *config = [YMURLSessionConfiguration defaultSessionConfiguration]; 31 | config.timeoutIntervalForRequest = 8; 32 | YMURLSession *session = [YMURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; 33 | self.task = [session taskWithURL:URL]; 34 | [self.task resume]; 35 | } 36 | 37 | - (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didReceiveData:(NSData *)data { 38 | NSDictionary *value = [NSJSONSerialization JSONObjectWithData:data 39 | options:NSJSONReadingMutableContainers 40 | error:nil]; 41 | self.result = value; 42 | self.args = value[@"args"]; 43 | } 44 | 45 | - (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didCompleteWithError:(NSError *)error { 46 | [self.dataTaskExpectation fulfill]; 47 | if (!error) return; 48 | if (self.cancelExpectation) { 49 | [self.cancelExpectation fulfill]; 50 | } 51 | 52 | self.error = true; 53 | } 54 | 55 | - (void)cancel { 56 | [self.task cancel]; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /Example/Tests/Tasks/YMDownloadTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMDownloadTask.h 3 | // YMHTTP_Example 4 | // 5 | // Created by zymxxxs on 2020/3/5. 6 | // Copyright © 2020 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface YMDownloadTask : XCTestCase 16 | 17 | @property (atomic, strong) NSURL *location; 18 | @property (atomic, strong) XCTestExpectation *didDownloadExpectation; 19 | @property (atomic, strong) XCTestExpectation *didCompleteExpectation; 20 | @property (atomic, copy) YMURLSessionTask *task; 21 | @property (atomic, copy) NSString *expectationsDescription; 22 | @property (nullable, atomic, strong) XCTestCase *testCase; 23 | @property int64_t totalBytesWritten; 24 | @property (atomic, copy) void (^errorExpectation)(NSError *error); 25 | 26 | - (instancetype)initWithTestCase:(XCTestCase *)textCase description:(NSString *)description; 27 | 28 | - (void)makeDownloadExpectation; 29 | 30 | - (void)runWithRequest:(NSURLRequest *)request; 31 | 32 | - (void)runWithURL:(NSURL *)URL; 33 | 34 | - (void)runWithTask:(YMURLSessionTask *)task errorExpectation:(void (^)(void))errorExpectation; 35 | 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | -------------------------------------------------------------------------------- /Example/Tests/Tasks/YMDownloadTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMDownloadTask.m 3 | // YMHTTP_Example 4 | // 5 | // Created by zymxxxs on 2020/3/5. 6 | // Copyright © 2020 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import "YMDownloadTask.h" 10 | 11 | @implementation YMDownloadTask 12 | 13 | - (instancetype)initWithTestCase:(XCTestCase *)testCase description:(NSString *)description { 14 | self = [super init]; 15 | if (self) { 16 | self.expectationsDescription = description; 17 | self.testCase = testCase; 18 | self.didCompleteExpectation = 19 | [testCase expectationWithDescription:[NSString stringWithFormat:@"Did Complete %@", description]]; 20 | } 21 | return self; 22 | } 23 | 24 | - (void)makeDownloadExpectation { 25 | if (self.didDownloadExpectation != nil) return; 26 | self.didDownloadExpectation = [self.testCase 27 | expectationWithDescription:[NSString stringWithFormat:@"Did finish download: %@", self.description]]; 28 | self.testCase = nil; 29 | } 30 | 31 | - (void)runWithURL:(NSURL *)URL { 32 | [self makeDownloadExpectation]; 33 | YMURLSessionConfiguration *config = [YMURLSessionConfiguration defaultSessionConfiguration]; 34 | config.timeoutIntervalForRequest = 8; 35 | YMURLSession *session = [YMURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; 36 | YMURLSessionTask *task = [session taskWithDownloadURL:URL]; 37 | [task resume]; 38 | } 39 | 40 | - (void)runWithRequest:(NSURLRequest *)request { 41 | [self makeDownloadExpectation]; 42 | YMURLSessionConfiguration *config = [YMURLSessionConfiguration defaultSessionConfiguration]; 43 | config.timeoutIntervalForRequest = 8; 44 | YMURLSession *session = [YMURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; 45 | YMURLSessionTask *task = [session taskWithDownloadRequest:request]; 46 | [task resume]; 47 | } 48 | 49 | - (void)runWithTask:(YMURLSessionTask *)task errorExpectation:(void (^)(void))errorExpectation { 50 | // YMURLSessionConfiguration *config = [YMURLSessionConfiguration defaultSessionConfiguration]; 51 | // config.timeoutIntervalForRequest = 8; 52 | // YMURLSession *session = [YMURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; 53 | } 54 | 55 | - (void)YMURLSession:(YMURLSession *)session 56 | downloadTask:(YMURLSessionTask *)downloadTask 57 | didFinishDownloadingToURL:(NSURL *)location { 58 | if (self.errorExpectation) { 59 | NSLog(@"download: %@", downloadTask); 60 | NSLog(@"at location: %@", location); 61 | XCTFail(@"Expected an error, but got …didFinishDownloadingTo… from download task"); 62 | } else { 63 | NSError *e = nil; 64 | NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:location.path error:&e]; 65 | if (e) { 66 | XCTFail(@"Unable to calculate size of the downloaded file"); 67 | } else { 68 | XCTAssertEqual([attr[NSFileSize] integerValue], 69 | self.totalBytesWritten, 70 | @"Size of downloaded file not equal to total bytes downloaded"); 71 | } 72 | } 73 | self.location = location; 74 | [self.didDownloadExpectation fulfill]; 75 | } 76 | 77 | - (void)YMURLSession:(YMURLSession *)session 78 | downloadTask:(YMURLSessionTask *)downloadTask 79 | didWriteData:(int64_t)bytesWritten 80 | totalBytesWritten:(int64_t)totalBytesWritten 81 | totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { 82 | self.totalBytesWritten = totalBytesWritten; 83 | } 84 | 85 | - (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didCompleteWithError:(NSError *)error { 86 | if (self.errorExpectation) { 87 | if (error) { 88 | self.errorExpectation(error); 89 | } else { 90 | XCTFail(@"Expected an error, but got a completion without error from download task %@", task); 91 | } 92 | } else { 93 | if (error) { 94 | XCTAssertEqual(error.code, -1001, @"Unexpected error code"); 95 | } 96 | } 97 | [self.didCompleteExpectation fulfill]; 98 | } 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /Example/Tests/Tasks/YMHTTPRedirectionDataTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMHTTPRedirectionDataTask.h 3 | // YMHTTP_Tests 4 | // 5 | // Created by zymxxxs on 2020/3/6. 6 | // Copyright © 2020 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import "YMDataTask.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface YMHTTPRedirectionDataTask : YMDataTask 17 | 18 | @property (nonnull, nonatomic, strong) NSMutableArray *callbacks; 19 | @property (nonnull, nonatomic, strong) NSHTTPURLResponse *redirectionResponse; 20 | @property (nonnull, nonatomic, strong) NSHTTPURLResponse *response; 21 | @property (nonnull, nonatomic, strong) NSError *httpError; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /Example/Tests/Tasks/YMHTTPRedirectionDataTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMHTTPRedirectionDataTask.m 3 | // YMHTTP_Tests 4 | // 5 | // Created by zymxxxs on 2020/3/6. 6 | // Copyright © 2020 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import "YMHTTPRedirectionDataTask.h" 10 | 11 | @implementation YMHTTPRedirectionDataTask 12 | 13 | - (void)YMURLSession:(YMURLSession *)session 14 | task:(YMURLSessionTask *)task 15 | willPerformHTTPRedirection:(NSHTTPURLResponse *)response 16 | newRequest:(NSURLRequest *)request 17 | completionHandler:(void (^)(NSURLRequest *_Nullable))completionHandler { 18 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 19 | self.redirectionResponse = response; 20 | completionHandler(request); 21 | } 22 | 23 | - (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didCompleteWithError:(NSError *)error { 24 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 25 | [self.dataTaskExpectation fulfill]; 26 | if (self.cancelExpectation) { 27 | [self.cancelExpectation fulfill]; 28 | } 29 | 30 | self.error = error ? true : false; 31 | self.httpError = error; 32 | } 33 | 34 | - (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didReceiveData:(NSData *)data { 35 | NSString *last = (NSString *)self.callbacks.lastObject; 36 | if (![last isEqualToString:NSStringFromSelector(_cmd)]) { 37 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 38 | } 39 | NSDictionary *value = [NSJSONSerialization JSONObjectWithData:data 40 | options:NSJSONReadingMutableContainers 41 | error:nil]; 42 | self.result = value; 43 | } 44 | 45 | - (void)YMURLSession:(YMURLSession *)session 46 | task:(YMURLSessionTask *)task 47 | didReceiveResponse:(NSHTTPURLResponse *)response 48 | completionHandler:(void (^)(YMURLSessionResponseDisposition))completionHandler { 49 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 50 | self.response = response; 51 | completionHandler(YMURLSessionResponseAllow); 52 | } 53 | 54 | - (NSMutableArray *)callbacks { 55 | if (!_callbacks) { 56 | _callbacks = [NSMutableArray array]; 57 | } 58 | return _callbacks; 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /Example/Tests/Tasks/YMHTTPUploadDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMHTTPUploadDelegate.h 3 | // YMHTTP_Tests 4 | // 5 | // Created by zymxxxs on 2020/3/6. 6 | // Copyright © 2020 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface YMHTTPUploadDelegate : XCTestCase 16 | 17 | @property (nonnull, nonatomic, strong) NSMutableArray *callbacks; 18 | @property (nonnull, nonatomic, strong) XCTestExpectation *uploadCompletedExpectation; 19 | @property (nonnull, nonatomic, strong) NSInputStream *streamToProvideOnRequest; 20 | @property int64_t totalBytesSent; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /Example/Tests/Tasks/YMHTTPUploadDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMHTTPUploadDelegate.m 3 | // YMHTTP_Tests 4 | // 5 | // Created by zymxxxs on 2020/3/6. 6 | // Copyright © 2020 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import "YMHTTPUploadDelegate.h" 10 | 11 | @implementation YMHTTPUploadDelegate 12 | 13 | - (instancetype)init { 14 | self = [super init]; 15 | if (self) { 16 | self.callbacks = [[NSMutableArray alloc] init]; 17 | } 18 | return self; 19 | } 20 | 21 | - (void)YMURLSession:(YMURLSession *)session 22 | task:(YMURLSessionTask *)task 23 | didSendBodyData:(int64_t)bytesSent 24 | totalBytesSent:(int64_t)totalBytesSent 25 | totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { 26 | NSString *last = (NSString *)self.callbacks.lastObject; 27 | if (![last isEqualToString:NSStringFromSelector(_cmd)]) { 28 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 29 | } 30 | self.totalBytesSent = totalBytesSent; 31 | } 32 | 33 | - (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didCompleteWithError:(NSError *)error { 34 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 35 | [self.uploadCompletedExpectation fulfill]; 36 | } 37 | 38 | - (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didReceiveData:(NSData *)data { 39 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Example/Tests/Tasks/YMSessionDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMSessionDelegate.h 3 | // YMHTTP_Tests 4 | // 5 | // Created by zymxxxs on 2020/3/5. 6 | // Copyright © 2020 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface YMSessionDelegate : NSObject 16 | 17 | @property (nonnull, atomic, strong) XCTestExpectation *invalidateExpectation; 18 | @property (nonnull, atomic, strong) XCTestExpectation *cancelExpectation; 19 | @property (nonatomic, strong) XCTestExpectation *expectation; 20 | @property (nonatomic, strong) YMURLSession *session; 21 | @property (nonatomic, strong) YMURLSessionTask *task; 22 | @property (nonnull, nonatomic, strong) void (^redirectionHandler) 23 | (NSHTTPURLResponse *response, NSURLRequest *request, void (^completionHandler)(NSURLRequest *_Nullable)); 24 | @property (nonnull, nonatomic, strong) void (^newBodyStreamHandler)(void (^)(NSInputStream *_Nullable)); 25 | @property (nonnull, nonatomic, strong) NSMutableData *receivedData; 26 | @property (nonnull, nonatomic, strong) NSError *error; 27 | @property (nonnull, nonatomic, strong) NSHTTPURLResponse *response; 28 | @property (nonatomic, strong) NSHTTPURLResponse *redirectionResponse; 29 | @property (nonnull, nonatomic, strong) NSMutableArray *callbacks; 30 | @property (nonatomic) int64_t totalBytesSent; 31 | ; 32 | 33 | - (instancetype)initWithExpectation:(XCTestExpectation *)expectation; 34 | 35 | - (void)runWithRequest:(NSURLRequest *)request; 36 | - (void)runUploadTask:(NSURLRequest *)request; 37 | 38 | - (void)runWithURL:(NSURL *)URL; 39 | 40 | @end 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /Example/Tests/Tasks/YMSessionDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMSessionDelegate.m 3 | // YMHTTP_Tests 4 | // 5 | // Created by zymxxxs on 2020/3/5. 6 | // Copyright © 2020 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import "YMSessionDelegate.h" 10 | 11 | @implementation YMSessionDelegate 12 | 13 | - (instancetype)initWithExpectation:(XCTestExpectation *)expectation { 14 | self = [super init]; 15 | if (self) { 16 | self.expectation = expectation; 17 | } 18 | return self; 19 | } 20 | 21 | - (void)runWithRequest:(NSURLRequest *)request { 22 | YMURLSessionConfiguration *config = [YMURLSessionConfiguration defaultSessionConfiguration]; 23 | config.timeoutIntervalForRequest = 8; 24 | YMURLSession *session = [YMURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; 25 | self.task = [session taskWithRequest:request]; 26 | [self.task resume]; 27 | } 28 | 29 | - (void)runWithURL:(NSURL *)URL { 30 | YMURLSessionConfiguration *config = [YMURLSessionConfiguration defaultSessionConfiguration]; 31 | config.timeoutIntervalForRequest = 8; 32 | YMURLSession *session = [YMURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; 33 | self.task = [session taskWithURL:URL]; 34 | [self.task resume]; 35 | } 36 | 37 | - (void)runUploadTask:(NSURLRequest *)request { 38 | YMURLSessionConfiguration *config = [YMURLSessionConfiguration defaultSessionConfiguration]; 39 | config.timeoutIntervalForRequest = 8; 40 | YMURLSession *session = [YMURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; 41 | self.task = [session taskWithStreamedRequest:request]; 42 | [self.task resume]; 43 | } 44 | 45 | - (void)YMURLSession:(YMURLSession *)session didBecomeInvalidWithError:(NSError *)error { 46 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 47 | self.error = error; 48 | [self.invalidateExpectation fulfill]; 49 | } 50 | 51 | - (NSMutableArray *)callbacks { 52 | if (!_callbacks) { 53 | _callbacks = [NSMutableArray array]; 54 | } 55 | return _callbacks; 56 | } 57 | 58 | - (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didCompleteWithError:(NSError *)error { 59 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 60 | self.error = error; 61 | [self.expectation fulfill]; 62 | } 63 | 64 | - (void)YMURLSession:(YMURLSession *)session 65 | task:(YMURLSessionTask *)task 66 | needNewBodyStream:(void (^)(NSInputStream *_Nullable))completionHandler { 67 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 68 | 69 | if (_newBodyStreamHandler) { 70 | _newBodyStreamHandler(completionHandler); 71 | } 72 | } 73 | 74 | - (void)YMURLSession:(YMURLSession *)session 75 | task:(YMURLSessionTask *)task 76 | willPerformHTTPRedirection:(NSHTTPURLResponse *)response 77 | newRequest:(NSURLRequest *)request 78 | completionHandler:(void (^)(NSURLRequest *_Nullable))completionHandler { 79 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 80 | self.redirectionResponse = response; 81 | 82 | if (self.redirectionHandler) { 83 | self.redirectionHandler(response, request, completionHandler); 84 | } else { 85 | completionHandler(request); 86 | } 87 | } 88 | 89 | - (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didReceiveData:(NSData *)data { 90 | NSString *last = (NSString *)self.callbacks.lastObject; 91 | if (![last isEqualToString:NSStringFromSelector(_cmd)]) { 92 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 93 | } 94 | 95 | if (self.receivedData == nil) { 96 | self.receivedData = [data mutableCopy]; 97 | } else { 98 | [self.receivedData appendData:data]; 99 | } 100 | } 101 | 102 | - (void)YMURLSession:(YMURLSession *)session 103 | task:(YMURLSessionTask *)task 104 | didSendBodyData:(int64_t)bytesSent 105 | totalBytesSent:(int64_t)totalBytesSent 106 | totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { 107 | NSString *last = (NSString *)self.callbacks.lastObject; 108 | if (![last isEqualToString:NSStringFromSelector(_cmd)]) { 109 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 110 | } 111 | self.totalBytesSent = totalBytesSent; 112 | } 113 | 114 | - (void)YMURLSession:(YMURLSession *)session 115 | task:(YMURLSessionTask *)task 116 | didReceiveResponse:(NSHTTPURLResponse *)response 117 | completionHandler:(void (^)(YMURLSessionResponseDisposition))completionHandler { 118 | [self.callbacks addObject:NSStringFromSelector(_cmd)]; 119 | self.response = response; 120 | completionHandler(YMURLSessionResponseAllow); 121 | } 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /Example/Tests/TestURLSessionCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // TestURLSessionCache.m 3 | // YMHTTP_Tests 4 | // 5 | // Created by zymxxxs on 2020/3/8. 6 | // Copyright © 2020 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "YMCacheDataTask.h" 12 | 13 | @interface TestURLSessionCache : XCTestCase 14 | 15 | @end 16 | 17 | @implementation TestURLSessionCache 18 | 19 | - (void)resetURLCache { 20 | NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 diskCapacity:0 diskPath:nil]; 21 | [NSURLCache setSharedURLCache:URLCache]; 22 | } 23 | 24 | - (void)testCacheUseProtocolCachePolicy { 25 | [self resetURLCache]; 26 | 27 | NSString *urlString = @"http://httpbin.org/cache/200"; 28 | NSURL *url = [NSURL URLWithString:urlString]; 29 | NSURLRequest *request = [NSURLRequest requestWithURL:url]; 30 | XCTestExpectation *te = [self expectationWithDescription:@"GET testCacheUseProtocolCachePolicy: with a delegate"]; 31 | YMCacheDataTask *d = [[YMCacheDataTask alloc] initWithExpectation:te]; 32 | d.responseReceivedExpectation = [self expectationWithDescription:@"GET responseReceivedExpectation"]; 33 | d.disposition = YMURLSessionResponseAllow; 34 | [d runWithRequest:request]; 35 | [self waitForExpectationsWithTimeout:12 handler:nil]; 36 | 37 | if (!d.error) { 38 | NSCachedURLResponse *cachedURLResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; 39 | XCTAssertNotNil(cachedURLResponse); 40 | XCTAssertTrue([cachedURLResponse.response isKindOfClass:[NSHTTPURLResponse class]]); 41 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)cachedURLResponse.response; 42 | XCTAssertEqualObjects(response.allHeaderFields, d.response.allHeaderFields); 43 | } else { 44 | XCTFail(); 45 | } 46 | 47 | XCTestExpectation *te1 = [self expectationWithDescription:@"GET testCacheUseProtocolCachePolicy1: with a delegate"]; 48 | YMCacheDataTask *d1 = [[YMCacheDataTask alloc] initWithExpectation:te1]; 49 | d1.responseReceivedExpectation = [self expectationWithDescription:@"GET responseReceivedExpectation1"]; 50 | d1.disposition = YMURLSessionResponseAllow; 51 | [d1 runWithRequest:request]; 52 | [self waitForExpectationsWithTimeout:12 handler:nil]; 53 | 54 | if (!d1.error) { 55 | NSCachedURLResponse *cachedURLResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; 56 | XCTAssertNotNil(cachedURLResponse); 57 | XCTAssertTrue([cachedURLResponse.response isKindOfClass:[NSHTTPURLResponse class]]); 58 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)cachedURLResponse.response; 59 | XCTAssertEqualObjects(response.allHeaderFields, d1.response.allHeaderFields); 60 | } else { 61 | XCTFail(); 62 | } 63 | } 64 | 65 | - (void)testCacheUseIgnoringLocalCacheData { 66 | [self resetURLCache]; 67 | 68 | NSString *urlString = @"http://httpbin.org/cache/200"; 69 | NSURL *url = [NSURL URLWithString:urlString]; 70 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 71 | XCTestExpectation *te = 72 | [self expectationWithDescription:@"GET testCacheUseIgnoringLocalCacheData: with a delegate"]; 73 | YMCacheDataTask *d = [[YMCacheDataTask alloc] initWithExpectation:te]; 74 | d.disposition = YMURLSessionResponseAllow; 75 | [d runWithRequest:request]; 76 | [self waitForExpectationsWithTimeout:12 handler:nil]; 77 | 78 | if (!d.error) { 79 | NSCachedURLResponse *cachedURLResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; 80 | XCTAssertNotNil(cachedURLResponse); 81 | XCTAssertTrue([cachedURLResponse.response isKindOfClass:[NSHTTPURLResponse class]]); 82 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)cachedURLResponse.response; 83 | XCTAssertEqualObjects(response.allHeaderFields, d.response.allHeaderFields); 84 | } else { 85 | XCTFail(); 86 | } 87 | 88 | NSCachedURLResponse *cachedURLResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; 89 | XCTAssertNotNil(cachedURLResponse); 90 | sleep(1); 91 | 92 | XCTestExpectation *te1 = [self expectationWithDescription:@"GET testCacheUseProtocolCachePolicy1: with a delegate"]; 93 | YMCacheDataTask *d1 = [[YMCacheDataTask alloc] initWithExpectation:te1]; 94 | d1.disposition = YMURLSessionResponseAllow; 95 | request.cachePolicy = NSURLRequestReloadIgnoringCacheData; 96 | [d1 runWithRequest:request]; 97 | [self waitForExpectationsWithTimeout:12 handler:nil]; 98 | 99 | if (!d1.error) { 100 | XCTAssertTrue([cachedURLResponse.response isKindOfClass:[NSHTTPURLResponse class]]); 101 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)cachedURLResponse.response; 102 | XCTAssertNotEqualObjects(response.allHeaderFields, d1.response.allHeaderFields); 103 | XCTAssertNotEqualObjects(response.allHeaderFields[@"Date"], d1.response.allHeaderFields[@"Date"]); 104 | } else { 105 | XCTFail(); 106 | } 107 | } 108 | 109 | - (void)testCacheUseReturnCacheDataElseLoad { 110 | [self resetURLCache]; 111 | 112 | NSString *urlString = @"http://httpbin.org/cache/200"; 113 | NSURL *url = [NSURL URLWithString:urlString]; 114 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 115 | XCTestExpectation *te = 116 | [self expectationWithDescription:@"GET testCacheUseReturnCacheDataElseLoad: with a delegate"]; 117 | YMCacheDataTask *d = [[YMCacheDataTask alloc] initWithExpectation:te]; 118 | d.disposition = YMURLSessionResponseAllow; 119 | [d runWithRequest:request]; 120 | [self waitForExpectationsWithTimeout:12 handler:nil]; 121 | 122 | if (!d.error) { 123 | NSCachedURLResponse *cachedURLResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; 124 | XCTAssertNotNil(cachedURLResponse); 125 | XCTAssertTrue([cachedURLResponse.response isKindOfClass:[NSHTTPURLResponse class]]); 126 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)cachedURLResponse.response; 127 | XCTAssertEqualObjects(response.allHeaderFields, d.response.allHeaderFields); 128 | } else { 129 | XCTFail(); 130 | } 131 | 132 | NSCachedURLResponse *cachedURLResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; 133 | XCTAssertNotNil(cachedURLResponse); 134 | 135 | XCTestExpectation *te1 = 136 | [self expectationWithDescription:@"GET testCacheUseReturnCacheDataElseLoad 1: with a delegate"]; 137 | YMCacheDataTask *d1 = [[YMCacheDataTask alloc] initWithExpectation:te1]; 138 | d1.disposition = YMURLSessionResponseAllow; 139 | request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; 140 | [d1 runWithRequest:request]; 141 | [self waitForExpectationsWithTimeout:12 handler:nil]; 142 | 143 | if (!d1.error) { 144 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)cachedURLResponse.response; 145 | XCTAssertEqualObjects(response.allHeaderFields, d1.response.allHeaderFields); 146 | } else { 147 | XCTFail(); 148 | } 149 | 150 | [self resetURLCache]; 151 | XCTAssertNil([[NSURLCache sharedURLCache] cachedResponseForRequest:request]); 152 | sleep(2); 153 | 154 | XCTestExpectation *te2 = 155 | [self expectationWithDescription:@"GET testCacheUseReturnCacheDataElseLoad 2: with a delegate"]; 156 | YMCacheDataTask *d2 = [[YMCacheDataTask alloc] initWithExpectation:te2]; 157 | d2.disposition = YMURLSessionResponseAllow; 158 | request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; 159 | [d2 runWithRequest:request]; 160 | [self waitForExpectationsWithTimeout:12 handler:nil]; 161 | 162 | if (!d2.error) { 163 | XCTAssertTrue([cachedURLResponse.response isKindOfClass:[NSHTTPURLResponse class]]); 164 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)cachedURLResponse.response; 165 | XCTAssertNotEqualObjects(response.allHeaderFields, d2.response.allHeaderFields); 166 | XCTAssertNotEqualObjects(response.allHeaderFields[@"Date"], d2.response.allHeaderFields[@"Date"]); 167 | } else { 168 | XCTFail(); 169 | } 170 | } 171 | 172 | - (void)testCacheUseReturnCacheDataDontLoad { 173 | [[NSURLCache sharedURLCache] removeAllCachedResponses]; 174 | 175 | NSString *urlString = @"http://httpbin.org/cache/200"; 176 | NSURL *url = [NSURL URLWithString:urlString]; 177 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 178 | XCTestExpectation *te = 179 | [self expectationWithDescription:@"GET testCacheUseReturnCacheDataDontLoad: with a delegate"]; 180 | YMCacheDataTask *d = [[YMCacheDataTask alloc] initWithExpectation:te]; 181 | d.disposition = YMURLSessionResponseAllow; 182 | [d runWithRequest:request]; 183 | [self waitForExpectationsWithTimeout:12 handler:nil]; 184 | 185 | if (!d.error) { 186 | NSCachedURLResponse *cachedURLResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; 187 | XCTAssertNotNil(cachedURLResponse); 188 | XCTAssertTrue([cachedURLResponse.response isKindOfClass:[NSHTTPURLResponse class]]); 189 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)cachedURLResponse.response; 190 | XCTAssertEqualObjects(response.allHeaderFields, d.response.allHeaderFields); 191 | } else { 192 | XCTFail(); 193 | } 194 | 195 | NSCachedURLResponse *cachedURLResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; 196 | XCTAssertNotNil(cachedURLResponse); 197 | 198 | XCTestExpectation *te1 = 199 | [self expectationWithDescription:@"GET testCacheUseReturnCacheDataDontLoad 1: with a delegate"]; 200 | YMCacheDataTask *d1 = [[YMCacheDataTask alloc] initWithExpectation:te1]; 201 | d1.disposition = YMURLSessionResponseAllow; 202 | request.cachePolicy = NSURLRequestReturnCacheDataDontLoad; 203 | [d1 runWithRequest:request]; 204 | [self waitForExpectationsWithTimeout:12 handler:nil]; 205 | 206 | if (!d1.error) { 207 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)cachedURLResponse.response; 208 | XCTAssertEqualObjects(response.allHeaderFields, d1.response.allHeaderFields); 209 | } else { 210 | XCTFail(); 211 | } 212 | 213 | [self resetURLCache]; 214 | XCTAssertNil([[NSURLCache sharedURLCache] cachedResponseForRequest:request]); 215 | 216 | XCTestExpectation *te2 = 217 | [self expectationWithDescription:@"GET testCacheUseReturnCacheDataDontLoad 2: with a delegate"]; 218 | YMCacheDataTask *d2 = [[YMCacheDataTask alloc] initWithExpectation:te2]; 219 | d2.disposition = YMURLSessionResponseAllow; 220 | request.cachePolicy = NSURLRequestReturnCacheDataDontLoad; 221 | [d2 runWithRequest:request]; 222 | [self waitForExpectationsWithTimeout:12 handler:nil]; 223 | XCTAssertTrue(d2.error); 224 | } 225 | 226 | - (void)testCacheUsingResponseAllow { 227 | [self resetURLCache]; 228 | 229 | NSString *urlString = @"http://httpbin.org/get"; 230 | NSURL *url = [NSURL URLWithString:urlString]; 231 | NSURLRequest *request = [NSURLRequest requestWithURL:url]; 232 | XCTestExpectation *te = [self expectationWithDescription:@"GET testCacheUsingResponseAllow: with a delegate"]; 233 | YMCacheDataTask *d = [[YMCacheDataTask alloc] initWithExpectation:te]; 234 | d.responseReceivedExpectation = [self expectationWithDescription:@"GET responseReceivedExpectation"]; 235 | d.disposition = YMURLSessionResponseCancel; 236 | [d runWithRequest:request]; 237 | [self waitForExpectationsWithTimeout:12 handler:nil]; 238 | 239 | if (!d.error) { 240 | XCTFail(); 241 | } else { 242 | XCTAssertEqual(d.task.error.code, NSURLErrorCancelled); 243 | } 244 | } 245 | 246 | @end 247 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // The contents of this file are implicitly included at the beginning of every test case source file. 2 | 3 | #ifdef __OBJC__ 4 | 5 | 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /Example/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/YMHTTP.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/YMHTTP.xcodeproj/xcshareddata/xcschemes/YMHTTP-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 39 | 40 | 41 | 42 | 48 | 49 | 50 | 51 | 53 | 59 | 60 | 61 | 62 | 63 | 73 | 75 | 81 | 82 | 83 | 84 | 90 | 92 | 98 | 99 | 100 | 101 | 103 | 104 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /Example/YMHTTP.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/YMHTTP.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/YMHTTP/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Example/YMHTTP/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Example/YMHTTP/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Example/YMHTTP/YMAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMAppDelegate.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 12/31/2019. 6 | // Copyright (c) 2019 zymxxxs. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface YMAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/YMHTTP/YMAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMAppDelegate.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 12/31/2019. 6 | // Copyright (c) 2019 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import "YMAppDelegate.h" 10 | 11 | @implementation YMAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Example/YMHTTP/YMHTTP-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleLocalizations 16 | 17 | zh_CN 18 | 19 | CFBundleName 20 | ${PRODUCT_NAME} 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 1.0 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | 1.0 29 | LSRequiresIPhoneOS 30 | 31 | NSAppTransportSecurity 32 | 33 | NSAllowsArbitraryLoads 34 | 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIMainStoryboardFile 39 | Main 40 | UIRequiredDeviceCapabilities 41 | 42 | armv7 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | 48 | UISupportedInterfaceOrientations~ipad 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationPortraitUpsideDown 52 | UIInterfaceOrientationLandscapeLeft 53 | UIInterfaceOrientationLandscapeRight 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Example/YMHTTP/YMHTTP-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | @import UIKit; 15 | @import Foundation; 16 | #endif 17 | -------------------------------------------------------------------------------- /Example/YMHTTP/YMViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMViewController.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 12/31/2019. 6 | // Copyright (c) 2019 zymxxxs. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface YMViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/YMHTTP/YMViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMViewController.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 12/31/2019. 6 | // Copyright (c) 2019 zymxxxs. All rights reserved. 7 | // 8 | 9 | #import "YMViewController.h" 10 | #import 11 | 12 | @interface YMViewController () 13 | 14 | @property (nonatomic, strong) NSURLSession *us; 15 | @property (nonatomic, strong) YMURLSession *yus; 16 | 17 | 18 | @end 19 | 20 | @implementation YMViewController 21 | 22 | - (void)viewDidLoad 23 | { 24 | [super viewDidLoad]; 25 | 26 | // 27 | // NSURLRequest *r1 = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://httpbin.org/get?a=b"]]; 28 | // dispatch_group_t group2 = dispatch_group_create(); 29 | // CFAbsoluteTime startYMURLSession = CFAbsoluteTimeGetCurrent(); 30 | // for (int i=0; i<10; i++) { 31 | // dispatch_group_enter(group2); 32 | // [[[YMURLSession sharedSession] taskWithRequest:r1 33 | // completionHandler:^(NSData * _Nullable data, NSHTTPURLResponse * _Nullable response, NSError * _Nullable error) { 34 | // if (error) { 35 | // NSLog(@"YMURLSession 失败了"); 36 | // } 37 | // dispatch_group_leave(group2); 38 | // }] resume]; 39 | // } 40 | // 41 | // dispatch_group_notify(group2, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 42 | // CFAbsoluteTime endYMURLSession = CFAbsoluteTimeGetCurrent(); 43 | // NSLog(@"YMURLSession 总共花费时间:%@", @(endYMURLSession - startYMURLSession)); 44 | // }); 45 | // 46 | // 47 | // NSURLRequest *r = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://httpbin.org/get"]]; 48 | // dispatch_group_t group1 = dispatch_group_create(); 49 | // CFAbsoluteTime startNSURLSession = CFAbsoluteTimeGetCurrent(); 50 | // for (int i=0; i<10; i++) { 51 | // dispatch_group_enter(group1); 52 | // [[[NSURLSession sharedSession] dataTaskWithRequest:r 53 | // completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 54 | // if (error) { 55 | // NSLog(@"NSURLSession 失败了"); 56 | // } 57 | // dispatch_group_leave(group1); 58 | // 59 | // }] resume]; 60 | // } 61 | // 62 | // dispatch_group_notify(group1, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 63 | // CFAbsoluteTime endNSURLSession = CFAbsoluteTimeGetCurrent(); 64 | // NSLog(@"NSURLSession 总共花费时间:%@", @(endNSURLSession - startNSURLSession)); 65 | // }); 66 | } 67 | 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /Example/YMHTTP/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/YMHTTP/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 12/31/2019. 6 | // Copyright (c) 2019 zymxxxs. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | #import "YMAppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) 13 | { 14 | @autoreleasepool { 15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([YMAppDelegate class])); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 zymxxxs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YMHTTP 2 | 3 | [![Build Status](https://travis-ci.org/zymxxxs/YMHTTP.svg?branch=master)](https://travis-ci.org/zymxxxs/YMHTTP) 4 | [![Version](https://img.shields.io/cocoapods/v/YMHTTP.svg?style=flat)](https://cocoapods.org/pods/YMHTTP) 5 | [![License](https://img.shields.io/cocoapods/l/YMHTTP.svg?style=flat)](https://cocoapods.org/pods/YMHTTP) 6 | [![Platform](https://img.shields.io/cocoapods/p/YMHTTP.svg?style=flat)](https://cocoapods.org/pods/YMHTTP) 7 | 8 | 9 | `YMHTTP` 是一个适用于 iOS 平台,基于 [libcurl](https://curl.haxx.se/) 的 IO 多路复用 HTTP 框架,其 API 设计和行为与 NSURLSession 保持高度一致。 10 | 11 | 因为 `YMHTTP` 是基于 libcurl 进行封装,所以有着较高的定制性,目前的版本与 `NSURLSession` 在 API 保持高度一致的同时拓展了 DNS 的能力(包括 SNI 的场景)。 12 | 13 | 如果你对 DNS 相关的问题比较感兴趣,可以查看这个 [文章](https://juejin.im/post/5e6a4cfc518825495b29ad65),它汇总了在 iOS 上支持 DNS 需要面临的一些技术难点、相关解决方案以及 YMHTTP 诞生的初衷。 14 | 15 | ## 说明 16 | 17 | 1. 您可以通过 [NSURLSession](https://developer.apple.com/documentation/foundation/nsurlsession) 来查阅具体的细节。 18 | 2. 这里有一份非常不错的[NSURLSession最全攻略](https://mp.weixin.qq.com/s/UdwRytczg2lar0FwRpLTyg)可以查漏补缺(来自搜狐技术产品)。 19 | 3. YMHTTP 和 NSURLSession 非常像,一个是 YM 前缀,一个是 NS 前缀,对外提供的API相互一致 20 | 4. 如果您已经非常了解 NSURLSession,那么可以直接查阅 Connect to specific host and port 部分来获取 DNS 相关内容 21 | 5. 不支持 System Background Task 相关功能,这个真的无能为力 22 | 23 | ## 安装 24 | 25 | ```ruby 26 | pod 'YMHTTP', '~> 1.0' 27 | ``` 28 | 29 | ## Requirements 30 | 31 | * iOS 10.0 32 | * Xcode 11.3.1 33 | * libcurl 7.64.1 + SecureTransport 34 | 35 | ## 使用 36 | 37 | ### 0x01 YMSession 38 | 39 | ```objc 40 | // 创建 sharedSession 41 | YMURLSession *sharedSession = [YMURLSession sharedSession]; 42 | 43 | // 使用指定配置创建会话 44 | YMURLSessionConfiguration *config = [YMURLSessionConfiguration defaultSessionConfiguration]; 45 | YMURLSession *sessionNoDelegate = [YMURLSession sessionWithConfiguration:config]; 46 | 47 | // 创建具有指定会话配置,委托和操作队列的会话 48 | YMURLSession *session = [YMURLSession sessionWithConfiguration:config 49 | delegate:self 50 | delegateQueue:nil]; 51 | ``` 52 | 53 | ### 0x02 Adding Data Task to a Session 54 | 55 | ```objc 56 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request; 57 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url; 58 | ``` 59 | 60 | 通过指定 URL 或 Request 来创建一个任务 61 | 62 | ```objc 63 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request 64 | completionHandler:(void (^)(NSData *_Nullable data, 65 | NSHTTPURLResponse *_Nullable response, 66 | NSError *_Nullable error))completionHandler; 67 | 68 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url 69 | completionHandler:(void (^)(NSData *_Nullable data, 70 | NSHTTPURLResponse *_Nullable response, 71 | NSError *_Nullable error))completionHandler; 72 | ``` 73 | 74 | 通过指定 URL 或 Request 来创建任务,在任务完成后调用 completionHandler 75 | 76 | #### Example 77 | 78 | 1. delegate 方式 79 | 80 | ```objc 81 | // create 82 | YMURLSessionTask *task = [session taskWithURL:[NSURL URLWithString:@"http://httpbin.org/get"]]; 83 | [task resume]; 84 | 85 | // delegate 86 | - (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didCompleteWithError:(NSError *)error { 87 | 88 | } 89 | 90 | - (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didReceiveData:(NSData *)data { 91 | 92 | } 93 | 94 | - (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse * _Nullable))completionHandler { 95 | completionHandler(proposedResponse); 96 | } 97 | 98 | -(void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(YMURLSessionResponseDisposition))completionHandler { 99 | completionHandler(YMURLSessionResponseAllow); 100 | } 101 | ``` 102 | 103 | 2. completionHandler 方式 104 | 105 | ```objc 106 | YMURLSessionTask *task = [session taskWithURL:[NSURL URLWithString:@"http://httpbin.org/get"] completionHandler:^(NSData * _Nullable data, NSHTTPURLResponse * _Nullable response, NSError * _Nullable error) { 107 | 108 | }]; 109 | [task resume]; 110 | ``` 111 | 112 | ### 0x03 Adding Upload Tasks to a Session 113 | 114 | ```objc 115 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL; 116 | 117 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData; 118 | 119 | - (YMURLSessionTask *)taskWithStreamedRequest:(NSURLRequest *)request; 120 | 121 | ``` 122 | 123 | 通过指定 Request 来创建一个上传任务。 124 | 125 | `taskWithStreamedRequest` 方法,会调用 `YMURLSession:task:needNewBodyStream:` delegate 方法,您需要通过 `completionHandler` 返回一个 `NSInputStream` 对象。当然你也可以功过 `NSURLMutableRequest` 创建对象,并在 `bodyStream` 传入 `NSInputStream` 对象。 126 | 127 | 如果您需要上传大文件,建议您使用 `fromFile` 方法,虽然 `taskWithStreamedRequest` 也支持大文件的传输,但其形式为循环执行 `读取指定长度内容 -> 上传该内容`,该行为在内部的线程是同步的,而 `fromFile` 方式则会每次异步获取 3 * CURL_MAX_WRITE_SIZE 长度的内容供 libcurl 进行上传(CURL_MAX_WRITE_SIZE 为单次支持最大上传的长度),不仅减少文件 IO 的次数,也减少同步阻塞的时间,优化上传效率。 128 | 129 | ```objc 130 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request 131 | fromFile:(NSURL *)fileURL 132 | completionHandler:(void (^)(NSData *_Nullable data, 133 | NSHTTPURLResponse *_Nullable response, 134 | NSError *_Nullable error))completionHandler; 135 | 136 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request 137 | fromData:(nullable NSData *)bodyData 138 | completionHandler:(void (^)(NSData *_Nullable data, 139 | NSHTTPURLResponse *_Nullable response, 140 | NSError *_Nullable error))completionHandler; 141 | ``` 142 | 143 | 通过指定 Request 来创建任务,在任务完成后调用 completionHandler 144 | 145 | 146 | ### 0x04 Adding Download Tasks to a Session 147 | 148 | ```objc 149 | - (YMURLSessionTask *)taskWithDownloadRequest:(NSURLRequest *)request; 150 | 151 | - (YMURLSessionTask *)taskWithDownloadURL:(NSURL *)url; 152 | ``` 153 | 154 | 通过指定 Request 来创建一个下载任务,并返回临时文件,由于该文件是临时文件,因此必须打开该文件进行读取,或将其移动到应用程序沙盒容器目录中的永久位置,支持大文件下载。 155 | 156 | 当然你也可以使用 `taskWithRequest` 和 `taskWithURL` 来自定义下载任务。 157 | 158 | ```objc 159 | - (YMURLSessionTask *)taskWithDownloadRequest:(NSURLRequest *)request 160 | completionHandler:(void (^)(NSURL *_Nullable location, 161 | NSHTTPURLResponse *_Nullable response, 162 | NSError *_Nullable error))completionHandler; 163 | 164 | - (YMURLSessionTask *)taskWithDownloadURL:(NSURL *)url 165 | completionHandler:(void (^)(NSURL *_Nullable location, 166 | NSHTTPURLResponse *_Nullable response, 167 | NSError *_Nullable error))completionHandler; 168 | ``` 169 | 170 | ### 0x05 Connect to specific host and port 171 | 172 | ```objc 173 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url connectToHost:(NSString *)host; 174 | 175 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url connectToHost:(NSString *)host connectToPort:(NSInteger)port; 176 | 177 | // 创建包含 host port 的 request 178 | [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://httpbin.org/get"] connectToHost:@"52.202.2.199"]; 179 | [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://httpbin.org/get"] connectToHost:@"52.202.2.199" connectToPort:443]; 180 | [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://httpbin.org/get"] 181 | connectToHost:@"52.202.2.199" 182 | connectToPort:443 183 | cachePolicy:NSURLRequestUseProtocolCachePolicy 184 | timeoutInterval:60]; 185 | ``` 186 | 187 | 连接到特定的主机和端口,其中 host 支持 IP 的形式。如果使用正常的域名+host+port的请求方式,那么对于框架内部可以自动处理 Cookie,Cache 以及 302 等问题,当然该接口也支持 SNI 的场景。 188 | 189 | 备注:该接口不会影响到 DNS Cache,了解更多可以看这里 https://curl.haxx.se/libcurl/c/CURLOPT_CONNECT_TO.html 190 | 191 | ## libcurl 192 | 193 | > libcurl is free, thread-safe, IPv6 compatible, feature rich, well supported, fast, thoroughly documented and is already used by many known, big and successful companies. 194 | 195 | > libcurl是免费的,线程安全的,IPv6兼容的,功能丰富,支持良好,速度快,有完整的文档记录,已经被许多知名的,大的和成功的公司使用。 196 | 197 | 您也可以通过这里查看更多的内容:https://curl.haxx.se/ 198 | 199 | ### libcurl 版本 200 | 当前使用 libcurl 7.64.1 的版本,与 macOS Catalina 中保持一致,使用[curl-android-ios](https://github.com/gcesarmza/curl-android-ios)进行构建,你也可以选择喜欢的版本进行构建 201 | 202 | ### HTTP/2 203 | 目前版本不支持 HTTP/2,你可以使用 [Build-OpenSSL-cURL](https://github.com/jasonacox/Build-OpenSSL-cURL.git) 进行构建支持 HTTP/2 功能的版本。 204 | 205 | 注意 `Build-OpenSSL-cURL` 中使用的是 openSSL,而目前 macOS Catalina 中则是使用 LibreSSL,我在 `Build-OpenSSL-cURL` 的基础上修改了一个支持 `libresll` 的版本,链接地址:https://github.com/zymxxxs/libcurl-nghttp2-libressl-ios 206 | 207 | 备注: 208 | * 支持 HTTP/2 需要考虑包大小的影响 209 | * 目前一期的工作量主要是对外接口与 NSURLSession 对齐以及支持 DNS,HTTP/2 暂时不在支持范围内,所以上述脚本只能保证构建出静态库,暂无做过较多的验证,请知悉。 210 | 211 | ## 最后 212 | 213 | 如果看过 `swift-corelibs-foundation` 的相关源码, 你会发现其 `NSURLSession` 系列的功能(HTTP、FTP)则是基于 libcurl 进行的封装。如果你想学习它,建议你直接去学习官方源码,YMHTTP 也是参考其中大量的实现,然后补充一些尚未实现的功能以及修复了一些BUG。 214 | 215 | `YMHTTP` 之所以诞生,初衷是为了 `彻底` 解决 HTTP DNS 的问题(性能+SNI 场景+ Cache + Cookies + 302 ),现在回头来看,倒像是切开了一道口,获取了更多的自由度,如果您需要什么功能,请 ISSUE 告诉我。 216 | 217 | ## 如何贡献 218 | 219 | 非常欢迎你的加入! [提一个Issue](https://github.com/zymxxxs/YMHTTP/issues/new) 或者提交一个 Pull Request。 220 | 221 | ## License 222 | 223 | **YMHTTP** is available under the MIT license. See the LICENSE file for more info. 224 | 225 | ## 感谢 226 | 227 | * [lindean](https://github.com/lindean) 破老师,目前就职于PDD。感谢其初版 HTTP DNS 的实现,作为先驱者,填了无数坑,尤其是 libcurl 中各种参数以及 Cache 层的相关实现 228 | * [amendgit](https://github.com/amendgit) 二老师,人称二哥,目前就职于支付宝,感谢其在 `IO 多路复用` 上解惑与指导 229 | * [libcurl](https://curl.haxx.se/libcurl/) 230 | * [swift-corelibs-foundation](https://github.com/apple/swift-corelibs-foundation.git) 231 | * [curl-android-ios](https://github.com/gcesarmza/curl-android-ios) 232 | * [Build-OpenSSL-cURL](https://github.com/jasonacox/Build-OpenSSL-cURL.git) 233 | 234 | ## TODO: 235 | 236 | * 目前指定 IP 的能力通过 CURLOPT_CONNECT_TO 来解决,其好处是不会影响 DNS Cache,但是在 Charles 中会直接显示 IP 的请求。待考虑是否替换为 CURLOPT_RESOLVE 参数,不过对于 DNS Cache 的问题,是需要影响还是不能影响需要删除?或者是说DNS的能力,使用 CURLOPT_CONNECT_TO 还是 CURLOPT_RESOLVE 哪一个更为合理? 237 | * 目前大部分还是基于 AFNetworking 进行分封装,待考虑是否提供一个 YMNetworking 版本便于接入?也可以参考 retrofit 的接口实现? 238 | * NSURLSessionTaskMetrics 待实现。使用 curl_easy_getinfo 实现,目前实现这个功能,代码改动较大,需要着重设计下,目前属于重要不紧急,可以延后。 239 | -------------------------------------------------------------------------------- /YMHTTP.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint YMHTTP.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'YMHTTP' 11 | s.version = '1.0.0' 12 | s.summary = '一个基于 libcurl 的 IO 多路复用 HTTP 框架' 13 | s.homepage = 'https://github.com/zymxxxs/YMHTTP' 14 | s.license = { :type => 'MIT', :file => 'LICENSE' } 15 | s.author = { 'zymxxxs' => 'zymxxxs@gmail.com' } 16 | s.source = { :git => 'https://github.com/zymxxxs/YMHTTP.git', :tag => s.version.to_s } 17 | 18 | s.ios.deployment_target = '10.0' 19 | 20 | s.source_files = 'YMHTTP/**/*.{h,m}' 21 | s.public_header_files = 'YMHTTP/*.h' 22 | s.exclude_files = 'YMHTTP/libcurl/**/*.h' 23 | 24 | s.subspec 'libcurl' do |ss| 25 | ss.source_files = 'YMHTTP/libcurl/**/*.h' 26 | ss.private_header_files = 'YMHTTP/libcurl/**/*.h' 27 | ss.vendored_libraries = 'YMHTTP/libcurl/libcurl.a' 28 | ss.ios.library = 'z' 29 | end 30 | 31 | end 32 | -------------------------------------------------------------------------------- /YMHTTP/NSURLRequest+YMCategory.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLRequest+YMCategory.h 3 | // Pods 4 | // 5 | // Created by zymxxxs on 2020/3/4. 6 | // 7 | #import 8 | 9 | NS_ASSUME_NONNULL_BEGIN 10 | 11 | @interface NSURLRequest (YMCategory) 12 | 13 | - (instancetype)initWithURL:(NSURL *)URL connectToHost:(NSString *)connectToHost; 14 | - (instancetype)initWithURL:(NSURL *)URL connectToHost:(NSString *)connectToHost connectToPort:(NSInteger)connectToPort; 15 | - (instancetype)initWithURL:(NSURL *)URL 16 | connectToHost:(NSString *)connectToHost 17 | connectToPort:(NSInteger)connectToPort 18 | cachePolicy:(NSURLRequestCachePolicy)cachePolicy 19 | timeoutInterval:(NSTimeInterval)timeoutInterval; 20 | 21 | @property (nullable, readonly, copy) NSString *ym_connectToHost; 22 | @property (readonly) NSInteger ym_connectToPort; 23 | 24 | @end 25 | 26 | @interface NSMutableURLRequest (YMCategory) 27 | 28 | @property (nullable, copy) NSString *ym_connectToHost; 29 | @property NSInteger ym_connectToPort; 30 | 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | -------------------------------------------------------------------------------- /YMHTTP/NSURLRequest+YMCategory.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLRequest+YMCategory.m 3 | // Pods 4 | // 5 | // Created by zymxxxs on 2020/3/4. 6 | // 7 | 8 | #import 9 | #import "NSURLRequest+YMCategory.h" 10 | 11 | @implementation NSURLRequest (YMCategory) 12 | 13 | + (void)load { 14 | ym_swizzleMethods([self class], @selector(copyWithZone:), @selector(ym_copyWithZone:)); 15 | ym_swizzleMethods([self class], @selector(mutableCopyWithZone:), @selector(ym_mutableCopyWithZone:)); 16 | } 17 | 18 | NS_INLINE void ym_swizzleMethods(Class class, SEL origSel, SEL swizSel) { 19 | Method origMethod = class_getInstanceMethod(class, origSel); 20 | Method swizMethod = class_getInstanceMethod(class, swizSel); 21 | 22 | BOOL didAddMethod = 23 | class_addMethod(class, origSel, method_getImplementation(swizMethod), method_getTypeEncoding(swizMethod)); 24 | if (didAddMethod) { 25 | class_replaceMethod(class, swizSel, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); 26 | } else { 27 | method_exchangeImplementations(origMethod, swizMethod); 28 | } 29 | } 30 | 31 | - (instancetype)initWithURL:(NSURL *)URL connectToHost:(NSString *)connectToHost { 32 | return [self initWithURL:URL connectToHost:connectToHost connectToPort:0]; 33 | } 34 | 35 | - (instancetype)initWithURL:(NSURL *)URL 36 | connectToHost:(NSString *)connectToHost 37 | connectToPort:(NSInteger)connectToPort { 38 | self = [self initWithURL:URL]; 39 | if (self) { 40 | self.ym_connectToHost = connectToHost; 41 | self.ym_connectToPort = connectToPort; 42 | } 43 | return self; 44 | } 45 | 46 | - (instancetype)initWithURL:(NSURL *)URL 47 | connectToHost:(NSString *)connectToHost 48 | connectToPort:(NSInteger)connectToPort 49 | cachePolicy:(NSURLRequestCachePolicy)cachePolicy 50 | timeoutInterval:(NSTimeInterval)timeoutInterval { 51 | self = [self initWithURL:URL cachePolicy:cachePolicy timeoutInterval:timeoutInterval]; 52 | if (self) { 53 | self.ym_connectToHost = connectToHost; 54 | self.ym_connectToPort = connectToPort; 55 | } 56 | return self; 57 | } 58 | 59 | - (void)setYm_connectToHost:(NSString *_Nullable)ym_connectToHost { 60 | objc_setAssociatedObject(self, @selector(ym_connectToHost), ym_connectToHost, OBJC_ASSOCIATION_COPY); 61 | } 62 | 63 | - (NSString *)ym_connectToHost { 64 | return objc_getAssociatedObject(self, _cmd); 65 | } 66 | 67 | - (void)setYm_connectToPort:(NSInteger)ym_connectToPort { 68 | NSNumber *value = nil; 69 | if (ym_connectToPort <= 0) ym_connectToPort = 0; 70 | value = [NSNumber numberWithInteger:ym_connectToPort]; 71 | 72 | objc_setAssociatedObject(self, @selector(ym_connectToPort), value, OBJC_ASSOCIATION_COPY); 73 | } 74 | 75 | - (NSInteger)ym_connectToPort { 76 | NSNumber *value = objc_getAssociatedObject(self, _cmd); 77 | return [value integerValue]; 78 | } 79 | 80 | - (id)ym_copyWithZone:(NSZone *)zone { 81 | NSURLRequest *r = [self ym_copyWithZone:zone]; 82 | r.ym_connectToHost = self.ym_connectToHost; 83 | r.ym_connectToPort = self.ym_connectToPort; 84 | return r; 85 | } 86 | 87 | - (id)ym_mutableCopyWithZone:(NSZone *)zone { 88 | NSURLRequest *r = [self ym_mutableCopyWithZone:zone]; 89 | r.ym_connectToHost = self.ym_connectToHost; 90 | r.ym_connectToPort = self.ym_connectToPort; 91 | return r; 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /YMHTTP/YMHTTP.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMHTTP.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/18. 6 | // 7 | 8 | #ifndef YMHTTP_h 9 | #define YMHTTP_h 10 | 11 | #import "NSURLRequest+YMCategory.h" 12 | #import "YMURLSession.h" 13 | #import "YMURLSessionConfiguration.h" 14 | #import "YMURLSessionDelegate.h" 15 | #import "YMURLSessionTask.h" 16 | 17 | #endif /* YMHTTP_h */ 18 | -------------------------------------------------------------------------------- /YMHTTP/YMURLSession.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLSession.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/3. 6 | // 7 | 8 | #import 9 | #import "YMURLSessionDelegate.h" 10 | 11 | @class YMURLSessionConfiguration; 12 | @class YMURLSessionTaskBehaviour; 13 | @class YMURLSessionTask; 14 | @class YMEasyHandle; 15 | @class YMTaskRegistry; 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | @interface YMURLSession : NSObject 20 | 21 | /// The shared session uses the currently set global NSURLCache, 22 | /// NSHTTPCookieStorage and NSURLCredentialStorage objects. 23 | @property (class, readonly, strong) YMURLSession *sharedSession; 24 | 25 | + (YMURLSession *)sessionWithConfiguration:(YMURLSessionConfiguration *)configuration; 26 | 27 | + (YMURLSession *)sessionWithConfiguration:(YMURLSessionConfiguration *)configuration 28 | delegate:(nullable id)delegate 29 | delegateQueue:(nullable NSOperationQueue *)queue; 30 | 31 | @property (readonly, strong) NSOperationQueue *delegateQueue; 32 | 33 | @property (nullable, readonly, retain) id delegate; 34 | 35 | @property (readonly, copy) YMURLSessionConfiguration *configuration; 36 | 37 | @property (nullable, copy) NSString *sessionDescription; 38 | 39 | @property (readonly, strong) dispatch_queue_t workQueue; 40 | 41 | - (void)finishTasksAndInvalidate; 42 | 43 | - (void)invalidateAndCancel; 44 | 45 | - (void)resetWithCompletionHandler:(void (^)(void))completionHandler; 46 | 47 | - (void)flushWithCompletionHandler:(void (^)(void))completionHandler; 48 | 49 | - (void)getAllTasksWithCompletionHandler:(void (^)(NSArray<__kindof YMURLSessionTask *> *tasks))completionHandler; 50 | 51 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request; 52 | 53 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url; 54 | 55 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url connectToHost:(NSString *)host; 56 | 57 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url connectToHost:(NSString *)host connectToPort:(NSInteger)port; 58 | 59 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request 60 | completionHandler:(void (^)(NSData *_Nullable data, 61 | NSHTTPURLResponse *_Nullable response, 62 | NSError *_Nullable error))completionHandler; 63 | 64 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url 65 | completionHandler:(void (^)(NSData *_Nullable data, 66 | NSHTTPURLResponse *_Nullable response, 67 | NSError *_Nullable error))completionHandler; 68 | 69 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url 70 | connectToHost:(NSString *)host 71 | completionHandler:(void (^)(NSData *_Nullable data, 72 | NSHTTPURLResponse *_Nullable response, 73 | NSError *_Nullable error))completionHandler; 74 | 75 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL; 76 | 77 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData; 78 | 79 | - (YMURLSessionTask *)taskWithStreamedRequest:(NSURLRequest *)request; 80 | 81 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request 82 | fromFile:(NSURL *)fileURL 83 | completionHandler:(void (^)(NSData *_Nullable data, 84 | NSHTTPURLResponse *_Nullable response, 85 | NSError *_Nullable error))completionHandler; 86 | 87 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request 88 | fromData:(nullable NSData *)bodyData 89 | completionHandler:(void (^)(NSData *_Nullable data, 90 | NSHTTPURLResponse *_Nullable response, 91 | NSError *_Nullable error))completionHandler; 92 | 93 | - (YMURLSessionTask *)taskWithDownloadRequest:(NSURLRequest *)request; 94 | 95 | - (YMURLSessionTask *)taskWithDownloadURL:(NSURL *)url; 96 | 97 | - (YMURLSessionTask *)taskWithDownloadRequest:(NSURLRequest *)request 98 | completionHandler:(void (^)(NSURL *_Nullable location, 99 | NSHTTPURLResponse *_Nullable response, 100 | NSError *_Nullable error))completionHandler; 101 | 102 | - (YMURLSessionTask *)taskWithDownloadURL:(NSURL *)url 103 | completionHandler:(void (^)(NSURL *_Nullable location, 104 | NSHTTPURLResponse *_Nullable response, 105 | NSError *_Nullable error))completionHandler; 106 | 107 | #pragma mark - Private 108 | 109 | @property (nonatomic, strong) YMTaskRegistry *taskRegistry; 110 | 111 | - (YMURLSessionTaskBehaviour *)behaviourForTask:(YMURLSessionTask *)task; 112 | 113 | - (void)addHandle:(YMEasyHandle *)handle; 114 | 115 | - (void)removeHandle:(YMEasyHandle *)handle; 116 | 117 | - (void)updateTimeoutTimerToValue:(NSInteger)value; 118 | 119 | - (instancetype)init __attribute__((unavailable( 120 | "Please use NSURLSessionConfiguration.defaultSessionConfiguration or other class methods to create instances"))); 121 | + (instancetype)new __attribute__((unavailable( 122 | "Please use NSURLSessionConfiguration.defaultSessionConfiguration or other class methods to create instances"))); 123 | 124 | @end 125 | 126 | NS_ASSUME_NONNULL_END 127 | -------------------------------------------------------------------------------- /YMHTTP/YMURLSession.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLSession.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/3. 6 | // 7 | 8 | #import "YMURLSession.h" 9 | #import "NSURLRequest+YMCategory.h" 10 | #import "YMMacro.h" 11 | #import "YMMultiHandle.h" 12 | #import "YMTaskRegistry.h" 13 | #import "YMURLSessionConfiguration.h" 14 | #import "YMURLSessionTask.h" 15 | #import "YMURLSessionTaskBehaviour.h" 16 | #import "YMURLSessionTaskBody.h" 17 | 18 | static YMURLSession *_sharedSession = nil; 19 | 20 | static dispatch_queue_t _globalVarSyncQ = nil; 21 | static int sessionCounter = 0; 22 | NS_INLINE int nextSessionIdentifier() { 23 | if (_globalVarSyncQ == nil) { 24 | _globalVarSyncQ = dispatch_queue_create("com.zymxxxs.URLSession.GlobalVarSyncQ", DISPATCH_QUEUE_SERIAL); 25 | } 26 | dispatch_sync(_globalVarSyncQ, ^{ 27 | sessionCounter += 1; 28 | }); 29 | return sessionCounter; 30 | } 31 | 32 | @interface YMURLSession () 33 | 34 | @property (nonatomic, strong) YMMultiHandle *multiHandle; 35 | @property (readwrite, strong) dispatch_queue_t workQueue; 36 | @property int identifier; 37 | @property BOOL invalidated; 38 | @property NSUInteger nextTaskIdentifier; 39 | @property (nullable, retain) id delegate; 40 | @property (readwrite, copy) YMURLSessionConfiguration *configuration; 41 | @property (readwrite, strong) NSOperationQueue *delegateQueue; 42 | 43 | @end 44 | 45 | @implementation YMURLSession 46 | 47 | #pragma mark - Public Methods 48 | 49 | - (instancetype)initWithConfiguration:(YMURLSessionConfiguration *)configuration 50 | delegate:(id)delegate 51 | delegateQueue:(NSOperationQueue *)queue { 52 | self = [super init]; 53 | if (self) { 54 | self.taskRegistry = [[YMTaskRegistry alloc] init]; 55 | ym_initializeLibcurl(); 56 | self.identifier = nextSessionIdentifier(); 57 | self.nextTaskIdentifier = 1; 58 | NSString *queueLabel = [NSString stringWithFormat:@"YMURLSession %@", @(self.identifier)]; 59 | self.workQueue = dispatch_queue_create([queueLabel UTF8String], DISPATCH_QUEUE_SERIAL); 60 | if (queue) { 61 | self.delegateQueue = queue; 62 | } else { 63 | self.delegateQueue = [[NSOperationQueue alloc] init]; 64 | } 65 | self.delegateQueue.maxConcurrentOperationCount = 1; 66 | self.delegate = delegate; 67 | self.configuration = configuration; 68 | self.multiHandle = [[YMMultiHandle alloc] initWithConfiguration:_configuration WorkQueue:self.workQueue]; 69 | } 70 | return self; 71 | } 72 | 73 | + (YMURLSession *)sharedSession { 74 | static dispatch_once_t onceToken; 75 | dispatch_once(&onceToken, ^{ 76 | YMURLSessionConfiguration *configuration = [YMURLSessionConfiguration defaultSessionConfiguration]; 77 | _sharedSession = [YMURLSession sessionWithConfiguration:configuration delegate:nil delegateQueue:nil]; 78 | }); 79 | return _sharedSession; 80 | } 81 | 82 | + (YMURLSession *)sessionWithConfiguration:(YMURLSessionConfiguration *)configuration 83 | delegate:(id)delegate 84 | delegateQueue:(NSOperationQueue *)queue { 85 | return [[YMURLSession alloc] initWithConfiguration:configuration delegate:delegate delegateQueue:queue]; 86 | } 87 | 88 | + (YMURLSession *)sessionWithConfiguration:(YMURLSessionConfiguration *)configuration { 89 | return [self sessionWithConfiguration:configuration delegate:nil delegateQueue:nil]; 90 | } 91 | 92 | - (void)finishTasksAndInvalidate { 93 | dispatch_async(self.workQueue, ^{ 94 | self.invalidated = true; 95 | 96 | void (^invalidateSessionCallback)(void) = ^{ 97 | if (!self.delegate) return; 98 | [self.delegateQueue addOperationWithBlock:^{ 99 | if ([self.delegate respondsToSelector:@selector(YMURLSession:didBecomeInvalidWithError:)]) { 100 | [self.delegate YMURLSession:self didBecomeInvalidWithError:nil]; 101 | } 102 | self.delegate = nil; 103 | }]; 104 | }; 105 | 106 | if (!self.taskRegistry.isEmpty) { 107 | [self.taskRegistry notifyOnTasksCompletion:invalidateSessionCallback]; 108 | } else { 109 | invalidateSessionCallback(); 110 | } 111 | }); 112 | } 113 | 114 | - (void)invalidateAndCancel { 115 | if (self == _sharedSession) return; 116 | 117 | dispatch_sync(self.workQueue, ^{ 118 | self.invalidated = true; 119 | }); 120 | 121 | for (YMURLSessionTask *task in self.taskRegistry.allTasks) { 122 | [task cancel]; 123 | } 124 | 125 | dispatch_async(self.workQueue, ^{ 126 | if (!self.delegate) return; 127 | [self.delegateQueue addOperationWithBlock:^{ 128 | if ([self.delegate respondsToSelector:@selector(YMURLSession:didBecomeInvalidWithError:)]) { 129 | [self.delegate YMURLSession:self didBecomeInvalidWithError:nil]; 130 | } 131 | self.delegate = nil; 132 | }]; 133 | }); 134 | } 135 | 136 | - (void)resetWithCompletionHandler:(void (^)(void))completionHandler { 137 | dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); 138 | dispatch_async(globalQ, ^{ 139 | if (self.configuration.URLCache) { 140 | [self.configuration.URLCache removeAllCachedResponses]; 141 | } 142 | 143 | NSURLCredentialStorage *storage = self.configuration.URLCredentialStorage; 144 | if (storage) { 145 | for (NSURLProtectionSpace *protectionSpace in storage.allCredentials) { 146 | NSDictionary *credentialEntry = storage.allCredentials[protectionSpace]; 147 | for (NSURLCredential *credential in credentialEntry.allValues) { 148 | [storage removeCredential:credential forProtectionSpace:protectionSpace]; 149 | } 150 | } 151 | } 152 | [self flushWithCompletionHandler:completionHandler]; 153 | }); 154 | } 155 | 156 | - (void)flushWithCompletionHandler:(void (^)(void))completionHandler { 157 | if (completionHandler) { 158 | [_delegateQueue addOperationWithBlock:^{ 159 | completionHandler(); 160 | }]; 161 | } 162 | } 163 | 164 | - (void)getAllTasksWithCompletionHandler:(void (^)(NSArray<__kindof YMURLSessionTask *> *_Nonnull))completionHandler { 165 | dispatch_async(self.workQueue, ^{ 166 | [self.delegateQueue addOperationWithBlock:^{ 167 | NSMutableArray *tasks = [[NSMutableArray alloc] init]; 168 | for (YMURLSessionTask *task in self.taskRegistry.allTasks) { 169 | if (task.state == YMURLSessionTaskStateRunning || task.isSuspendedAfterResume) { 170 | [tasks addObject:task]; 171 | } 172 | } 173 | if (completionHandler) completionHandler(tasks); 174 | }]; 175 | }); 176 | } 177 | 178 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url { 179 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] init]; 180 | return [self taskWithRequest:url behaviour:b]; 181 | } 182 | 183 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url connectToHost:(NSString *)host { 184 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] init]; 185 | NSURLRequest *r = [[NSURLRequest alloc] initWithURL:url connectToHost:host]; 186 | return [self taskWithRequest:r behaviour:b]; 187 | } 188 | 189 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url connectToHost:(NSString *)host connectToPort:(NSInteger)port { 190 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] init]; 191 | NSURLRequest *r = [[NSURLRequest alloc] initWithURL:url connectToHost:host connectToPort:port]; 192 | return [self taskWithRequest:r behaviour:b]; 193 | } 194 | 195 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request { 196 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] init]; 197 | return [self taskWithRequest:request behaviour:b]; 198 | } 199 | 200 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url 201 | completionHandler: 202 | (void (^)(NSData *_Nullable, NSHTTPURLResponse *_Nullable, NSError *_Nullable))completionHandler { 203 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] init]; 204 | b.type = YMURLSessionTaskBehaviourTypeDataHandler; 205 | b.dataTaskCompeltion = completionHandler; 206 | return [self taskWithRequest:url behaviour:b]; 207 | } 208 | 209 | - (YMURLSessionTask *)taskWithURL:(NSURL *)url 210 | connectToHost:(NSString *)host 211 | completionHandler: 212 | (void (^)(NSData *_Nullable, NSHTTPURLResponse *_Nullable, NSError *_Nullable))completionHandler { 213 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] init]; 214 | b.type = YMURLSessionTaskBehaviourTypeDataHandler; 215 | b.dataTaskCompeltion = completionHandler; 216 | NSURLRequest *r = [[NSURLRequest alloc] initWithURL:url connectToHost:host]; 217 | return [self taskWithRequest:r behaviour:b]; 218 | } 219 | 220 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request 221 | completionHandler:(void (^)(NSData *_Nullable, NSHTTPURLResponse *_Nullable, NSError *_Nullable)) 222 | completionHandler { 223 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] init]; 224 | b.type = YMURLSessionTaskBehaviourTypeDataHandler; 225 | b.dataTaskCompeltion = completionHandler; 226 | return [self taskWithRequest:request behaviour:b]; 227 | } 228 | 229 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL { 230 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] init]; 231 | b.type = YMURLSessionTaskBehaviourTypeTaskDelegate; 232 | YMURLSessionTaskBody *body = [[YMURLSessionTaskBody alloc] initWithFileURL:fileURL]; 233 | return [self taskWithRequest:request body:body behaviour:b]; 234 | } 235 | 236 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData { 237 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] init]; 238 | b.type = YMURLSessionTaskBehaviourTypeTaskDelegate; 239 | YMURLSessionTaskBody *body = [[YMURLSessionTaskBody alloc] initWithData:bodyData]; 240 | return [self taskWithRequest:request body:body behaviour:b]; 241 | } 242 | 243 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request 244 | fromData:(NSData *)bodyData 245 | completionHandler:(void (^)(NSData *_Nullable, NSHTTPURLResponse *_Nullable, NSError *_Nullable)) 246 | completionHandler { 247 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] initWithDataTaskCompeltion:completionHandler]; 248 | YMURLSessionTaskBody *body = [[YMURLSessionTaskBody alloc] initWithData:bodyData]; 249 | return [self taskWithRequest:request body:body behaviour:b]; 250 | } 251 | 252 | - (YMURLSessionTask *)taskWithRequest:(NSURLRequest *)request 253 | fromFile:(NSURL *)fileURL 254 | completionHandler:(void (^)(NSData *_Nullable, NSHTTPURLResponse *_Nullable, NSError *_Nullable)) 255 | completionHandler { 256 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] initWithDataTaskCompeltion:completionHandler]; 257 | YMURLSessionTaskBody *body = [[YMURLSessionTaskBody alloc] initWithFileURL:fileURL]; 258 | return [self taskWithRequest:request body:body behaviour:b]; 259 | } 260 | 261 | - (YMURLSessionTask *)taskWithStreamedRequest:(NSURLRequest *)request { 262 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] init]; 263 | b.type = YMURLSessionTaskBehaviourTypeTaskDelegate; 264 | return [self taskWithRequest:request body:nil behaviour:b]; 265 | } 266 | 267 | - (YMURLSessionTask *)taskWithDownloadURL:(NSURL *)url { 268 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] init]; 269 | YMURLSessionTask *task = [self taskWithRequest:url behaviour:b]; 270 | // TODO: this is ugly, need to fix it 271 | [task setValue:[NSNumber numberWithBool:true] forKey:@"isDownloadTask"]; 272 | return task; 273 | } 274 | 275 | - (YMURLSessionTask *)taskWithDownloadRequest:(NSURLRequest *)request { 276 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] init]; 277 | YMURLSessionTask *task = [self taskWithRequest:request behaviour:b]; 278 | [task setValue:[NSNumber numberWithBool:true] forKey:@"isDownloadTask"]; 279 | return task; 280 | } 281 | 282 | - (YMURLSessionTask *)taskWithDownloadURL:(NSURL *)url 283 | completionHandler:(void (^)(NSURL *_Nullable, NSHTTPURLResponse *_Nullable, NSError *_Nullable)) 284 | completionHandler { 285 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] initWithDownloadTaskCompeltion:completionHandler]; 286 | YMURLSessionTask *task = [self taskWithRequest:url behaviour:b]; 287 | [task setValue:[NSNumber numberWithBool:true] forKey:@"isDownloadTask"]; 288 | return task; 289 | } 290 | 291 | - (YMURLSessionTask *)taskWithDownloadRequest:(NSURLRequest *)request 292 | completionHandler:(void (^)(NSURL *_Nullable, 293 | NSHTTPURLResponse *_Nullable, 294 | NSError *_Nullable))completionHandler { 295 | YMURLSessionTaskBehaviour *b = [[YMURLSessionTaskBehaviour alloc] initWithDownloadTaskCompeltion:completionHandler]; 296 | YMURLSessionTask *task = [self taskWithRequest:request behaviour:b]; 297 | [task setValue:[NSNumber numberWithBool:true] forKey:@"isDownloadTask"]; 298 | return task; 299 | } 300 | 301 | - (YMURLSessionTaskBehaviour *)behaviourForTask:(YMURLSessionTask *)task { 302 | YMURLSessionTaskBehaviour *b = [_taskRegistry behaviourForTask:task]; 303 | if (b.type == YMURLSessionTaskBehaviourTypeTaskDelegate) { 304 | if (!(self.delegate && [self.delegate conformsToProtocol:@protocol(YMURLSessionTaskDelegate)])) { 305 | b.type = YMURLSessionTaskBehaviourTypeNoDelegate; 306 | } 307 | } 308 | return b; 309 | } 310 | 311 | - (void)addHandle:(YMEasyHandle *)handle { 312 | [self.multiHandle addHandle:handle]; 313 | } 314 | 315 | - (void)removeHandle:(YMEasyHandle *)handle { 316 | [self.multiHandle removeHandle:handle]; 317 | } 318 | 319 | - (void)updateTimeoutTimerToValue:(NSInteger)value { 320 | [self.multiHandle updateTimeoutTimerToValue:value]; 321 | } 322 | 323 | #pragma mark - Private Methods 324 | 325 | - (YMURLSessionTask *)taskWithRequest:(id)request behaviour:(YMURLSessionTaskBehaviour *)behaviour { 326 | if (self.invalidated) { 327 | YM_FATALERROR(@"Session invalidated"); 328 | } 329 | NSURLRequest *r = [self createConfiguredRequestFrom:request]; 330 | NSUInteger i = [self createNextTaskIdentifier]; 331 | YMURLSessionTask *task = [[YMURLSessionTask alloc] initWithSession:self reqeust:r taskIdentifier:i]; 332 | dispatch_async(self.workQueue, ^{ 333 | [self.taskRegistry addWithTask:task behaviour:behaviour]; 334 | }); 335 | return task; 336 | } 337 | 338 | - (YMURLSessionTask *)taskWithRequest:(id)request 339 | body:(YMURLSessionTaskBody *)body 340 | behaviour:(YMURLSessionTaskBehaviour *)behaviour { 341 | if (self.invalidated) { 342 | YM_FATALERROR(@"Session invalidated"); 343 | } 344 | NSURLRequest *r = [self createConfiguredRequestFrom:request]; 345 | NSUInteger i = [self createNextTaskIdentifier]; 346 | YMURLSessionTask *task = [[YMURLSessionTask alloc] initWithSession:self reqeust:r taskIdentifier:i body:body]; 347 | dispatch_async(self.workQueue, ^{ 348 | [self.taskRegistry addWithTask:task behaviour:behaviour]; 349 | }); 350 | return task; 351 | } 352 | 353 | - (NSURLRequest *)createConfiguredRequestFrom:(id)request { 354 | NSURLRequest *r = nil; 355 | if ([request isKindOfClass:[NSURLRequest class]]) { 356 | r = [_configuration configureRequest:request]; 357 | } 358 | 359 | if ([request isKindOfClass:[NSURL class]]) { 360 | NSURL *URL = (NSURL *)request; 361 | r = [_configuration configureRequestWithURL:URL]; 362 | } 363 | 364 | return r; 365 | } 366 | 367 | - (NSUInteger)createNextTaskIdentifier { 368 | __block NSUInteger i; 369 | dispatch_sync(self.workQueue, ^{ 370 | i = self.nextTaskIdentifier; 371 | self.nextTaskIdentifier += 1; 372 | }); 373 | return i; 374 | } 375 | 376 | @end 377 | -------------------------------------------------------------------------------- /YMHTTP/YMURLSessionConfiguration.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLSessionConfiguration.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/3. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface YMURLSessionConfiguration : NSObject 13 | 14 | @property (class, readonly, strong) YMURLSessionConfiguration *defaultSessionConfiguration; 15 | 16 | /// same to `defaultSessionConfiguration` 17 | @property (class, readonly, strong) YMURLSessionConfiguration *configuration; 18 | 19 | /// default cache policy for requests 20 | @property NSURLRequestCachePolicy requestCachePolicy; 21 | 22 | /// default timeout for requests. This will cause a timeout if no data is transmitted for the given timeout value, and 23 | /// is reset whenever data is transmitted. 24 | @property NSTimeInterval timeoutIntervalForRequest; 25 | 26 | /// default timeout for requests. This will cause a timeout if a resource is not able to be retrieved within a given 27 | /// timeout. 28 | @property NSTimeInterval timeoutIntervalForResource; 29 | 30 | /// Allow the use of HTTP pipelining 31 | @property BOOL HTTPShouldUsePipelining; 32 | 33 | /// Allow the session to set cookies on requests 34 | @property BOOL HTTPShouldSetCookies; 35 | 36 | /// Policy for accepting cookies. This overrides the policy otherwise specified by the cookie storage. 37 | @property NSHTTPCookieAcceptPolicy HTTPCookieAcceptPolicy; 38 | 39 | /// Specifies additional headers which will be set on outgoing requests. 40 | /// Note that these headers are added to the request only if not already present. 41 | @property (nullable, copy) NSDictionary *HTTPAdditionalHeaders; 42 | 43 | /// The maximum number of simultanous persistent connections per host 44 | @property NSInteger HTTPMaximumConnectionsPerHost; 45 | 46 | /// The cookie storage object to use, or nil to indicate that no cookies should be handled 47 | @property (nullable, strong) NSHTTPCookieStorage *HTTPCookieStorage; 48 | 49 | /// The credential storage object, or nil to indicate that no credential storage is to be used 50 | @property (nullable, strong) NSURLCredentialStorage *URLCredentialStorage; 51 | 52 | /// The URL resource cache, or nil to indicate that no caching is to be performed 53 | @property (nullable, strong) NSURLCache *URLCache; 54 | 55 | - (NSURLRequest *)configureRequest:(NSURLRequest *)request; 56 | 57 | - (NSURLRequest *)configureRequestWithURL:(NSURL *)URL; 58 | 59 | - (NSURLRequest *)setCookiesOnReqeust:(NSURLRequest *)request; 60 | 61 | - (instancetype)init __attribute__((unavailable( 62 | "Please use NSURLSessionConfiguration.defaultSessionConfiguration or other class methods to create instances"))); 63 | + (instancetype)new __attribute__((unavailable( 64 | "Please use NSURLSessionConfiguration.defaultSessionConfiguration or other class methods to create instances"))); 65 | 66 | @end 67 | 68 | NS_ASSUME_NONNULL_END 69 | -------------------------------------------------------------------------------- /YMHTTP/YMURLSessionConfiguration.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMSessionConfiguration.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/3. 6 | // 7 | 8 | #import "YMURLSessionConfiguration.h" 9 | 10 | @implementation YMURLSessionConfiguration 11 | 12 | - (instancetype)init { 13 | self = [super init]; 14 | if (self) { 15 | self.requestCachePolicy = NSURLRequestUseProtocolCachePolicy; 16 | self.timeoutIntervalForRequest = 60; 17 | self.timeoutIntervalForResource = 604800; 18 | self.HTTPShouldUsePipelining = false; 19 | self.HTTPShouldSetCookies = true; 20 | self.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain; 21 | self.HTTPAdditionalHeaders = nil; 22 | self.HTTPMaximumConnectionsPerHost = 6; 23 | self.HTTPCookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 24 | self.URLCredentialStorage = [NSURLCredentialStorage sharedCredentialStorage]; 25 | self.URLCache = [NSURLCache sharedURLCache]; 26 | } 27 | return self; 28 | } 29 | 30 | + (YMURLSessionConfiguration *)defaultSessionConfiguration { 31 | return [[self alloc] init]; 32 | } 33 | 34 | + (YMURLSessionConfiguration *)configuration { 35 | return [self defaultSessionConfiguration]; 36 | } 37 | 38 | - (NSURLRequest *)configureRequestWithURL:(NSURL *)URL { 39 | NSMutableURLRequest *r = [NSMutableURLRequest requestWithURL:URL]; 40 | r.cachePolicy = self.requestCachePolicy; 41 | r.timeoutInterval = self.timeoutIntervalForRequest; 42 | r.HTTPShouldUsePipelining = self.HTTPShouldUsePipelining; 43 | r.HTTPShouldHandleCookies = self.HTTPShouldSetCookies; 44 | return [self setCookiesOnReqeust:r]; 45 | } 46 | 47 | - (NSURLRequest *)configureRequest:(NSURLRequest *)request { 48 | return [self setCookiesOnReqeust:request]; 49 | } 50 | 51 | - (NSURLRequest *)setCookiesOnReqeust:(NSURLRequest *)request { 52 | NSMutableURLRequest *r = [request mutableCopy]; 53 | if (self.HTTPShouldSetCookies) { 54 | if (self.HTTPCookieStorage && request.URL) { 55 | NSArray *cookies = [_HTTPCookieStorage cookiesForURL:request.URL]; 56 | if (cookies && cookies.count) { 57 | NSDictionary *cookiesHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies]; 58 | NSString *cookieValue = cookiesHeaderFields[@"Cookie"]; 59 | if (cookieValue && cookieValue.length) { 60 | [r setValue:cookieValue forHTTPHeaderField:@"Cookie"]; 61 | } 62 | } 63 | } 64 | } 65 | return [r copy]; 66 | } 67 | 68 | - (id)copyWithZone:(NSZone *)zone { 69 | YMURLSessionConfiguration *session = [[self.class alloc] init]; 70 | session.requestCachePolicy = self.requestCachePolicy; 71 | session.timeoutIntervalForRequest = self.timeoutIntervalForRequest; 72 | session.timeoutIntervalForResource = self.timeoutIntervalForResource; 73 | session.HTTPShouldUsePipelining = self.HTTPShouldUsePipelining; 74 | session.HTTPShouldSetCookies = self.HTTPShouldSetCookies; 75 | session.HTTPCookieAcceptPolicy = self.HTTPCookieAcceptPolicy; 76 | session.HTTPAdditionalHeaders = self.HTTPAdditionalHeaders; 77 | session.HTTPMaximumConnectionsPerHost = self.HTTPMaximumConnectionsPerHost; 78 | session.HTTPCookieStorage = self.HTTPCookieStorage; 79 | session.URLCredentialStorage = self.URLCredentialStorage; 80 | session.URLCache = self.URLCache; 81 | return session; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /YMHTTP/YMURLSessionDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLSessionDelegate.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/8. 6 | // 7 | 8 | #import 9 | 10 | @class YMURLSessionTask; 11 | @class YMURLSession; 12 | 13 | typedef NS_ENUM(NSInteger, YMURLSessionResponseDisposition) { 14 | YMURLSessionResponseCancel = 0, /* Cancel the load, this is the same as -[task cancel] */ 15 | YMURLSessionResponseAllow = 1, 16 | }; 17 | 18 | NS_ASSUME_NONNULL_BEGIN 19 | 20 | @protocol YMURLSessionDelegate 21 | @optional 22 | - (void)YMURLSession:(YMURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error; 23 | 24 | @end 25 | 26 | @protocol YMURLSessionTaskDelegate 27 | 28 | @optional 29 | - (void)YMURLSession:(YMURLSession *)session 30 | task:(YMURLSessionTask *)task 31 | needNewBodyStream:(void (^)(NSInputStream *_Nullable bodyStream))completionHandler; 32 | 33 | - (void)YMURLSession:(YMURLSession *)session 34 | task:(YMURLSessionTask *)task 35 | willPerformHTTPRedirection:(NSHTTPURLResponse *)response 36 | newRequest:(NSURLRequest *)request 37 | completionHandler:(void (^)(NSURLRequest *_Nullable))completionHandler; 38 | 39 | - (void)YMURLSession:(YMURLSession *)session 40 | task:(YMURLSessionTask *)task 41 | didSendBodyData:(int64_t)bytesSent 42 | totalBytesSent:(int64_t)totalBytesSent 43 | totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend; 44 | 45 | - (void)YMURLSession:(YMURLSession *)session 46 | task:(YMURLSessionTask *)task 47 | didCompleteWithError:(nullable NSError *)error; 48 | 49 | @end 50 | 51 | @protocol YMURLSessionDataDelegate 52 | 53 | @optional 54 | 55 | - (void)YMURLSession:(YMURLSession *)session 56 | task:(YMURLSessionTask *)task 57 | didReceiveResponse:(NSHTTPURLResponse *)response 58 | completionHandler:(void (^)(YMURLSessionResponseDisposition disposition))completionHandler; 59 | 60 | - (void)YMURLSession:(YMURLSession *)session 61 | task:(YMURLSessionTask *)task 62 | willCacheResponse:(NSCachedURLResponse *)proposedResponse 63 | completionHandler:(void (^)(NSCachedURLResponse *_Nullable cachedResponse))completionHandler; 64 | 65 | - (void)YMURLSession:(YMURLSession *)session task:(YMURLSessionTask *)task didReceiveData:(NSData *)data; 66 | 67 | @end 68 | 69 | @protocol YMURLSessionDownloadDelegate 70 | 71 | - (void)YMURLSession:(YMURLSession *)session 72 | downloadTask:(YMURLSessionTask *)downloadTask 73 | didFinishDownloadingToURL:(NSURL *)location; 74 | 75 | @optional 76 | - (void)YMURLSession:(YMURLSession *)session 77 | downloadTask:(YMURLSessionTask *)downloadTask 78 | didWriteData:(int64_t)bytesWritten 79 | totalBytesWritten:(int64_t)totalBytesWritten 80 | totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite; 81 | @end 82 | 83 | NS_ASSUME_NONNULL_END 84 | -------------------------------------------------------------------------------- /YMHTTP/YMURLSessionTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLSessionTask.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/5. 6 | // 7 | 8 | #import 9 | 10 | @class YMURLSession; 11 | @class YMURLSessionTaskBody; 12 | 13 | FOUNDATION_EXPORT const int64_t YMURLSessionTransferSizeUnknown; 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | typedef NS_ENUM(NSInteger, YMURLSessionTaskState) { 18 | YMURLSessionTaskStateRunning = 0, 19 | YMURLSessionTaskStateSuspended = 1, 20 | YMURLSessionTaskStateCanceling = 2, 21 | YMURLSessionTaskStateCompleted = 3, 22 | }; 23 | 24 | @interface YMURLSessionTask : NSObject 25 | 26 | @property (readonly) NSUInteger taskIdentifier; 27 | 28 | @property (nullable, readonly, copy) NSURLRequest *originalRequest; 29 | 30 | @property (nullable, readonly, copy) NSURLRequest *currentRequest; 31 | 32 | @property (nullable, readonly, copy) NSHTTPURLResponse *response; 33 | 34 | @property (nullable, readonly, copy) NSError *error; 35 | 36 | @property (readonly) YMURLSessionTaskState state; 37 | 38 | @property (readonly) NSProgress *progress; 39 | 40 | @property (readonly) int64_t countOfBytesReceived; 41 | 42 | @property (readonly) int64_t countOfBytesSent; 43 | 44 | @property (readonly) int64_t countOfBytesExpectedToSend; 45 | 46 | @property (readonly) int64_t countOfBytesExpectedToReceive; 47 | 48 | - (instancetype)initWithSession:(YMURLSession *)session 49 | reqeust:(NSURLRequest *)request 50 | taskIdentifier:(NSUInteger)taskIdentifier; 51 | 52 | - (instancetype)initWithSession:(YMURLSession *)session 53 | reqeust:(NSURLRequest *)request 54 | taskIdentifier:(NSUInteger)taskIdentifier 55 | body:(nullable YMURLSessionTaskBody *)body; 56 | 57 | - (void)suspend; 58 | 59 | - (void)resume; 60 | 61 | - (void)cancel; 62 | 63 | #pragma mark - Private 64 | 65 | @property (readonly) BOOL isSuspendedAfterResume; 66 | 67 | @end 68 | 69 | NS_ASSUME_NONNULL_END 70 | -------------------------------------------------------------------------------- /YMHTTP/core/NSArray+YMCategory.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+YMCategory.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/28. 6 | // 7 | #import 8 | 9 | NS_ASSUME_NONNULL_BEGIN 10 | 11 | @interface NSArray (YMCategory) 12 | 13 | - (NSArray *)ym_map:(id (^)(id object))block; 14 | - (NSArray *)ym_filter:(BOOL (^)(id object))block; 15 | 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /YMHTTP/core/NSArray+YMCategory.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+YMCategory.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/28. 6 | // 7 | 8 | #import "NSArray+YMCategory.h" 9 | 10 | @implementation NSArray (YMCategory) 11 | 12 | - (NSArray *)ym_map:(id (^)(id object))block { 13 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:self.count]; 14 | 15 | for (id object in self) { 16 | [array addObject:block(object) ?: [NSNull null]]; 17 | } 18 | 19 | return array; 20 | } 21 | 22 | - (NSArray *)ym_filter:(BOOL (^)(id object))block { 23 | return [self 24 | filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { 25 | return block(evaluatedObject); 26 | }]]; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /YMHTTP/core/NSInputStream+YMCategory.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSInputStream+YMCategory.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/16. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface NSInputStream (YMCategory) 13 | 14 | - (BOOL)ym_seekToPosition:(uint64_t)position; 15 | 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /YMHTTP/core/NSInputStream+YMCategory.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSInputStream+YMCategory.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/16. 6 | // 7 | 8 | #import "NSInputStream+YMCategory.h" 9 | 10 | @implementation NSInputStream (YMCategory) 11 | 12 | - (BOOL)ym_seekToPosition:(uint64_t)position { 13 | if (position <= 0) return false; 14 | 15 | if (position >= INT_MAX) return false; 16 | 17 | NSUInteger bufferSize = 1024; 18 | uint8_t buffer[1024]; 19 | NSUInteger remainingBytes = position; 20 | 21 | if (self.streamStatus == NSStreamStatusNotOpen) { 22 | [self open]; 23 | } 24 | 25 | while (remainingBytes > 0 && [self hasBytesAvailable]) { 26 | NSInteger read = [self read:buffer maxLength:MIN(bufferSize, remainingBytes)]; 27 | if (read == -1) return false; 28 | remainingBytes -= remainingBytes; 29 | } 30 | 31 | if (remainingBytes != 0) return false; 32 | 33 | return true; 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /YMHTTP/core/NSURLCache+YMCategory.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSCachedURLResponse+YMCategory.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/7. 6 | // 7 | 8 | #import 9 | 10 | @class YMURLSessionTask; 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface NSURLCache (YMCategory) 15 | 16 | - (void)ym_storeCachedResponse:(NSCachedURLResponse *)cachedResponse forDataTask:(YMURLSessionTask *)dataTask; 17 | - (void)ym_getCachedResponseForDataTask:(YMURLSessionTask *)dataTask 18 | completionHandler:(void (^)(NSCachedURLResponse *_Nullable cachedResponse))completionHandler; 19 | - (void)ym_removeCachedResponseForDataTask:(YMURLSessionTask *)dataTask; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /YMHTTP/core/NSURLCache+YMCategory.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSCachedURLResponse+YMCategory.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/7. 6 | // 7 | 8 | #import 9 | #import "NSURLCache+YMCategory.h" 10 | #import "YMURLSessionTask.h" 11 | 12 | @implementation NSURLCache (YMCategory) 13 | 14 | - (void)ym_removeCachedResponseForDataTask:(YMURLSessionTask *)dataTask { 15 | if (!dataTask.currentRequest) return; 16 | [self removeCachedResponseForRequest:dataTask.currentRequest]; 17 | } 18 | 19 | - (void)ym_storeCachedResponse:(NSCachedURLResponse *)cachedResponse forDataTask:(YMURLSessionTask *)dataTask { 20 | if (!dataTask.currentRequest) return; 21 | [self storeCachedResponse:cachedResponse forRequest:dataTask.currentRequest]; 22 | } 23 | 24 | - (void)ym_getCachedResponseForDataTask:(YMURLSessionTask *)dataTask 25 | completionHandler:(void (^)(NSCachedURLResponse *_Nullable))completionHandler { 26 | if (!dataTask.currentRequest) { 27 | completionHandler(nil); 28 | return; 29 | } 30 | dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); 31 | dispatch_async(globalQ, ^{ 32 | completionHandler([self cachedResponseForRequest:dataTask.currentRequest]); 33 | }); 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /YMHTTP/core/YMTaskRegistry.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMTaskRegistry.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/5. 6 | // 7 | 8 | #import 9 | 10 | @class YMURLSessionTask; 11 | @class YMURLSessionTaskBehaviour; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface YMTaskRegistry : NSObject 16 | 17 | @property (nonatomic, strong) NSMutableDictionary *tasks; 18 | @property (nonatomic, strong) NSMutableDictionary *behaviours; 19 | @property (readonly, nonatomic, strong) NSArray *allTasks; 20 | @property (readonly, nonatomic, assign) BOOL isEmpty; 21 | 22 | - (void)addWithTask:(YMURLSessionTask *)task behaviour:(YMURLSessionTaskBehaviour *)behaviour; 23 | 24 | - (void)removeWithTask:(YMURLSessionTask *)task; 25 | 26 | - (void)notifyOnTasksCompletion:(void (^)(void))tasksCompletion; 27 | 28 | - (YMURLSessionTaskBehaviour *)behaviourForTask:(YMURLSessionTask *)task; 29 | 30 | @end 31 | 32 | NS_ASSUME_NONNULL_END 33 | -------------------------------------------------------------------------------- /YMHTTP/core/YMTaskRegistry.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMTaskRegistry.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/5. 6 | // 7 | 8 | #import "YMTaskRegistry.h" 9 | #import "YMMacro.h" 10 | #import "YMURLSessionTask.h" 11 | 12 | @interface YMTaskRegistry () 13 | 14 | @property (nonatomic, strong) void (^tasksCompletion)(void); 15 | 16 | @end 17 | 18 | @implementation YMTaskRegistry 19 | 20 | - (instancetype)init { 21 | self = [super init]; 22 | if (self) { 23 | self.tasks = [[NSMutableDictionary alloc] init]; 24 | self.behaviours = [[NSMutableDictionary alloc] init]; 25 | } 26 | return self; 27 | } 28 | 29 | - (NSArray *)allTasks { 30 | return [self.tasks allValues]; 31 | } 32 | 33 | - (BOOL)isEmpty { 34 | return [self.tasks count] == 0; 35 | } 36 | 37 | - (void)notifyOnTasksCompletion:(void (^)(void))tasksCompletion { 38 | self.tasksCompletion = tasksCompletion; 39 | } 40 | 41 | - (void)addWithTask:(YMURLSessionTask *)task behaviour:(YMURLSessionTaskBehaviour *)behaviour { 42 | NSUInteger taskIdentifier = task.taskIdentifier; 43 | if (taskIdentifier == 0) { 44 | YM_FATALERROR(@"Invalid task identifier"); 45 | } 46 | NSString *identifier = @(taskIdentifier).stringValue; 47 | if (self.tasks[identifier]) { 48 | if ([self.tasks[identifier] isEqual:task]) { 49 | YM_FATALERROR(@"Trying to re-insert a task that's already in the registry."); 50 | } else { 51 | YM_FATALERROR( 52 | @"Trying to insert a task, but a different task with the same identifier is already in the registry."); 53 | } 54 | } 55 | self.tasks[identifier] = task; 56 | self.behaviours[identifier] = behaviour; 57 | } 58 | 59 | - (void)removeWithTask:(YMURLSessionTask *)task { 60 | NSUInteger taskIdentifier = task.taskIdentifier; 61 | if (taskIdentifier == 0) { 62 | YM_FATALERROR(@"Invalid task identifier"); 63 | } 64 | NSString *identifier = @(taskIdentifier).stringValue; 65 | if (!self.tasks[identifier]) { 66 | YM_FATALERROR(@"Trying to remove task, but it's not in the registry."); 67 | } 68 | [self.tasks removeObjectForKey:identifier]; 69 | 70 | if (!self.behaviours[identifier]) { 71 | YM_FATALERROR(@"Trying to remove task's behaviour, but it's not in the registry."); 72 | } 73 | [self.behaviours removeObjectForKey:identifier]; 74 | 75 | if (self.tasksCompletion && [self isEmpty]) { 76 | self.tasksCompletion(); 77 | } 78 | } 79 | 80 | - (YMURLSessionTaskBehaviour *)behaviourForTask:(YMURLSessionTask *)task { 81 | NSString *identifier = @(task.taskIdentifier).stringValue; 82 | if (self.behaviours[identifier]) 83 | return self.behaviours[identifier]; 84 | else { 85 | YM_FATALERROR(@"Trying to access a behaviour for a task that in not in the registry."); 86 | } 87 | return nil; 88 | } 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /YMHTTP/core/YMTransferState.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMTransferState.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/8. 6 | // 7 | 8 | #import 9 | #import "YMURLSessionTaskBodySource.h" 10 | 11 | @class YMParsedResponseHeader; 12 | @class YMDataDrain; 13 | @class YMURLSessionTaskBodySource; 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface YMTransferState : NSObject 18 | 19 | @property (nonatomic, strong) NSURL *url; 20 | @property (nonatomic, strong) YMParsedResponseHeader *parsedResponseHeader; 21 | @property (nullable, nonatomic, strong) NSHTTPURLResponse *response; 22 | @property (nullable, nonatomic, strong) id requestBodySource; 23 | @property (nonatomic, strong) YMDataDrain *bodyDataDrain; 24 | @property (readonly, nonatomic, assign) BOOL isHeaderComplete; 25 | 26 | - (instancetype)initWithURL:(NSURL *)url bodyDataDrain:(YMDataDrain *)bodyDataDrain; 27 | 28 | - (instancetype)initWithURL:(NSURL *)url 29 | parsedResponseHeader:(YMParsedResponseHeader *)parsedResponseHeader 30 | response:(nullable NSHTTPURLResponse *)response 31 | bodySource:(nullable id)bodySource 32 | bodyDataDrain:(YMDataDrain *)bodyDataDrain; 33 | 34 | - (instancetype)initWithURL:(NSURL *)url 35 | bodyDataDrain:(YMDataDrain *)bodyDataDrain 36 | bodySource:(nullable id)bodySource; 37 | 38 | - (instancetype)byAppendingBodyData:(NSData *)bodyData; 39 | - (nullable instancetype)byAppendingHTTPHeaderLineData:(NSData *)data error:(NSError **)error; 40 | 41 | @end 42 | 43 | typedef NS_ENUM(NSUInteger, YMDataDrainType) { 44 | YMDataDrainInMemory, 45 | YMDataDrainTypeToFile, 46 | YMDataDrainTypeIgnore, 47 | }; 48 | 49 | @interface YMDataDrain : NSObject 50 | 51 | @property (nonatomic, assign) YMDataDrainType type; 52 | @property (nullable, nonatomic, strong) NSData *data; 53 | @property (nullable, nonatomic, strong) NSURL *fileURL; 54 | @property (nullable, nonatomic, strong) NSFileHandle *fileHandle; 55 | 56 | @end 57 | 58 | typedef NS_ENUM(NSUInteger, YMParsedResponseHeaderType) { 59 | YMParsedResponseHeaderTypePartial, 60 | YMParsedResponseHeaderTypeComplete 61 | }; 62 | 63 | @interface YMParsedResponseHeader : NSObject 64 | 65 | @property (nonatomic, assign) YMParsedResponseHeaderType type; 66 | @property (nonatomic, strong) NSArray *lines; 67 | 68 | - (nullable instancetype)byAppendingHeaderLine:(NSData *)data; 69 | 70 | - (nullable NSHTTPURLResponse *)createHTTPURLResponseForURL:(NSURL *)URL; 71 | 72 | @end 73 | 74 | NS_ASSUME_NONNULL_END 75 | -------------------------------------------------------------------------------- /YMHTTP/core/YMTransferState.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMTransferState.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/8. 6 | // 7 | 8 | #import "YMTransferState.h" 9 | 10 | #define YM_DELIMITERS_CR 0x0d 11 | #define YM_DELIMITERS_LR 0x0a 12 | 13 | @implementation YMTransferState 14 | 15 | - (instancetype)initWithURL:(NSURL *)url bodyDataDrain:(YMDataDrain *)bodyDataDrain { 16 | self = [super init]; 17 | if (self) { 18 | self.url = url; 19 | self.parsedResponseHeader = [[YMParsedResponseHeader alloc] init]; 20 | self.response = nil; 21 | self.requestBodySource = nil; 22 | self.bodyDataDrain = bodyDataDrain; 23 | } 24 | return self; 25 | } 26 | 27 | - (instancetype)initWithURL:(NSURL *)url 28 | bodyDataDrain:(YMDataDrain *)bodyDataDrain 29 | bodySource:(id)bodySource { 30 | self = [super init]; 31 | if (self) { 32 | self.url = url; 33 | self.parsedResponseHeader = [[YMParsedResponseHeader alloc] init]; 34 | self.response = nil; 35 | self.requestBodySource = bodySource; 36 | self.bodyDataDrain = bodyDataDrain; 37 | } 38 | return self; 39 | } 40 | 41 | - (instancetype)initWithURL:(NSURL *)url 42 | parsedResponseHeader:(YMParsedResponseHeader *)parsedResponseHeader 43 | response:(NSHTTPURLResponse *)response 44 | bodySource:(id)bodySource 45 | bodyDataDrain:(YMDataDrain *)bodyDataDrain { 46 | self = [super init]; 47 | if (self) { 48 | self.url = url; 49 | self.parsedResponseHeader = parsedResponseHeader; 50 | self.response = response; 51 | self.requestBodySource = bodySource; 52 | self.bodyDataDrain = bodyDataDrain; 53 | } 54 | return self; 55 | } 56 | 57 | - (instancetype)byAppendingBodyData:(NSData *)bodyData { 58 | switch (self.bodyDataDrain.type) { 59 | case YMDataDrainInMemory: { 60 | NSMutableData *data = 61 | self.bodyDataDrain.data ? [self.bodyDataDrain.data mutableCopy] : [NSMutableData data]; 62 | [data appendData:bodyData]; 63 | YMDataDrain *dataDrain = [[YMDataDrain alloc] init]; 64 | dataDrain.type = YMDataDrainInMemory; 65 | dataDrain.data = data; 66 | return [[YMTransferState alloc] initWithURL:self.url 67 | parsedResponseHeader:self.parsedResponseHeader 68 | response:self.response 69 | bodySource:self.requestBodySource 70 | bodyDataDrain:dataDrain]; 71 | } 72 | case YMDataDrainTypeToFile: { 73 | NSFileHandle *fileHandle = self.bodyDataDrain.fileHandle; 74 | [fileHandle seekToEndOfFile]; 75 | [fileHandle writeData:bodyData]; 76 | return self; 77 | } 78 | case YMDataDrainTypeIgnore: 79 | return self; 80 | } 81 | } 82 | 83 | - (instancetype)byAppendingHTTPHeaderLineData:(NSData *)data error:(NSError **)error { 84 | YMParsedResponseHeader *h = [self.parsedResponseHeader byAppendingHeaderLine:data]; 85 | if (!h) { 86 | if (error != NULL) { 87 | *error = [NSError errorWithDomain:NSURLErrorDomain 88 | code:-1 89 | userInfo:@{NSLocalizedDescriptionKey : @"YMHTTPParseSingleLineError"}]; 90 | } 91 | return nil; 92 | } 93 | 94 | if (h.type == YMParsedResponseHeaderTypeComplete) { 95 | NSHTTPURLResponse *response = [h createHTTPURLResponseForURL:self.url]; 96 | if (response == nil) { 97 | if (error != NULL) { 98 | *error = [NSError errorWithDomain:NSURLErrorDomain 99 | code:-1 100 | userInfo:@{NSLocalizedDescriptionKey : @"YMHTTPParseCompleteHeaderError"}]; 101 | } 102 | 103 | return nil; 104 | } 105 | 106 | YMParsedResponseHeader *ph = [[YMParsedResponseHeader alloc] init]; 107 | YMTransferState *ts = [[YMTransferState alloc] initWithURL:self.url 108 | parsedResponseHeader:ph 109 | response:response 110 | bodySource:self.requestBodySource 111 | bodyDataDrain:self.bodyDataDrain]; 112 | return ts; 113 | } else { 114 | YMTransferState *ts = [[YMTransferState alloc] initWithURL:self.url 115 | parsedResponseHeader:h 116 | response:nil 117 | bodySource:self.requestBodySource 118 | bodyDataDrain:self.bodyDataDrain]; 119 | return ts; 120 | } 121 | } 122 | 123 | - (BOOL)isHeaderComplete { 124 | return self.response != nil; 125 | } 126 | 127 | @end 128 | 129 | @implementation YMDataDrain 130 | 131 | @end 132 | 133 | @implementation YMParsedResponseHeader 134 | 135 | - (instancetype)init { 136 | self = [super init]; 137 | if (self) { 138 | self.type = YMParsedResponseHeaderTypePartial; 139 | self.lines = [[NSMutableArray alloc] init]; 140 | } 141 | return self; 142 | } 143 | 144 | - (instancetype)byAppendingHeaderLine:(NSData *)data { 145 | NSUInteger length = data.length; 146 | if (length >= 2) { 147 | uint8_t last2; 148 | uint8_t last1; 149 | [data getBytes:&last2 range:NSMakeRange(length - 2, 1)]; 150 | [data getBytes:&last1 range:NSMakeRange(length - 1, 1)]; 151 | if (last2 == YM_DELIMITERS_CR && last1 == YM_DELIMITERS_LR) { 152 | NSData *lineBuffer = [data subdataWithRange:NSMakeRange(0, length - 2)]; 153 | NSString *line = [[NSString alloc] initWithData:lineBuffer encoding:NSUTF8StringEncoding]; 154 | if (!line) return nil; 155 | return [self _byAppendingHeaderLine:line]; 156 | }; 157 | } 158 | 159 | return nil; 160 | } 161 | 162 | - (NSHTTPURLResponse *)createHTTPURLResponseForURL:(NSURL *)URL { 163 | NSString *head = [self.lines firstObject]; 164 | if (!head) return nil; 165 | 166 | if ([self.lines count] == 0) return nil; 167 | NSArray *tail = [self.lines subarrayWithRange:NSMakeRange(1, [self.lines count] - 1)]; 168 | 169 | NSArray *startline = [self statusLineFromLine:head]; 170 | if (!startline) return nil; 171 | 172 | NSDictionary *headerFields = [self createHeaderFieldsFromLines:tail]; 173 | 174 | NSString *v = startline[0]; 175 | NSString *s = startline[1]; 176 | return [[NSHTTPURLResponse alloc] initWithURL:URL 177 | statusCode:[s integerValue] 178 | HTTPVersion:v 179 | headerFields:headerFields]; 180 | } 181 | 182 | /// Split a request line into its 3 parts: HTTP-Version SP Status-Code SP Reason-Phrase CRLF 183 | /// - SeeAlso: https://tools.ietf.org/html/rfc2616#section-6.1 184 | - (NSArray *)statusLineFromLine:(NSString *)line { 185 | NSArray *a = [line componentsSeparatedByString:@" "]; 186 | if ([a count] < 3) return nil; 187 | 188 | NSString *s = a[1]; 189 | 190 | NSInteger status = [s integerValue]; 191 | if (status >= 100 && status <= 999) { 192 | return a; 193 | } else { 194 | return nil; 195 | } 196 | } 197 | 198 | - (NSDictionary *)createHeaderFieldsFromLines:(NSArray *)lines { 199 | // not same to swift's source 200 | NSMutableDictionary *headerFields = nil; 201 | for (NSString *line in lines) { 202 | NSRange r = [line rangeOfString:@":"]; 203 | if (r.location != NSNotFound) { 204 | NSString *head = [line substringToIndex:r.location]; 205 | NSString *tail = [line substringFromIndex:r.location + 1]; 206 | 207 | NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet]; 208 | NSString *key = [head stringByTrimmingCharactersInSet:set]; 209 | NSString *value = [tail stringByTrimmingCharactersInSet:set]; 210 | if (key && value) { 211 | if (!headerFields) headerFields = [NSMutableDictionary dictionary]; 212 | if (headerFields[key]) { 213 | NSString *v = [NSString stringWithFormat:@"%@, %@", headerFields[key], value]; 214 | [headerFields setObject:v forKey:key]; 215 | } else { 216 | headerFields[key] = value; 217 | } 218 | } 219 | } else { 220 | continue; 221 | } 222 | } 223 | return [headerFields copy]; 224 | } 225 | 226 | #pragma mark - Private Methods 227 | 228 | - (instancetype)_byAppendingHeaderLine:(NSString *)line { 229 | YMParsedResponseHeader *header = [[YMParsedResponseHeader alloc] init]; 230 | if (line.length == 0) { 231 | switch (self.type) { 232 | case YMParsedResponseHeaderTypePartial: { 233 | header.type = YMParsedResponseHeaderTypeComplete; 234 | header.lines = self.lines; 235 | return header; 236 | } 237 | case YMParsedResponseHeaderTypeComplete: 238 | return header; 239 | } 240 | } else { 241 | NSMutableArray *lines = [[self partialResponseHeader] mutableCopy]; 242 | [lines addObject:line]; 243 | 244 | header.type = YMParsedResponseHeaderTypePartial; 245 | header.lines = lines; 246 | 247 | return header; 248 | } 249 | } 250 | 251 | - (NSArray *)partialResponseHeader { 252 | switch (self.type) { 253 | case YMParsedResponseHeaderTypeComplete: 254 | return [NSArray array]; 255 | 256 | case YMParsedResponseHeaderTypePartial: 257 | return self.lines; 258 | } 259 | } 260 | 261 | @end 262 | -------------------------------------------------------------------------------- /YMHTTP/core/YMURLCacheHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLCacheHelper.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/28. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface YMURLCacheHelper : NSObject 13 | 14 | + (BOOL)canCacheResponse:(NSCachedURLResponse *)response request:(NSURLRequest *)request; 15 | 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /YMHTTP/core/YMURLCacheHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMCacheHelper.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/28. 6 | // 7 | 8 | #import "YMURLCacheHelper.h" 9 | #import "NSArray+YMCategory.h" 10 | 11 | @implementation YMURLCacheHelper 12 | 13 | NS_INLINE NSDate *dateFromString(NSString *v) { 14 | // https://tools.ietf.org/html/rfc2616#section-3.3.1 15 | 16 | static NSDateFormatter *df; 17 | if (!df) { 18 | df = [[NSDateFormatter alloc] init]; 19 | } 20 | 21 | // RFC 822 22 | df.dateFormat = @"EEE, dd MMM yyyy HH:mm:ss zzz"; 23 | NSDate *d1 = [df dateFromString:v]; 24 | if (d1) return d1; 25 | 26 | // RFC 850 27 | df.dateFormat = @"EEEE, dd-MMM-yy HH:mm:ss zzz"; 28 | NSDate *d2 = [df dateFromString:v]; 29 | if (d2) return d2; 30 | 31 | // ANSI C's asctime() format 32 | df.dateFormat = @"EEE MMM dd HH:mm:ss yy"; 33 | NSDate *d3 = [df dateFromString:v]; 34 | if (d3) return d3; 35 | 36 | return nil; 37 | } 38 | 39 | NS_INLINE NSInteger parseArgumentPart(NSString *part, NSString *name) { 40 | NSString *prefix = [NSString stringWithFormat:@"%@=", name]; 41 | if ([part hasPrefix:prefix]) { 42 | NSArray *split = [part componentsSeparatedByString:@"="]; 43 | if (split && [split count] == 2) { 44 | NSString *argument = split[1]; 45 | if ([argument hasPrefix:@"\""] && [argument hasSuffix:@"\""]) { 46 | if ([argument length] >= 2) { 47 | NSRange range = NSMakeRange(1, [argument length] - 2); 48 | argument = [argument substringWithRange:range]; 49 | return [argument integerValue]; 50 | } else 51 | return 0; 52 | } else { 53 | return [argument integerValue]; 54 | } 55 | } 56 | } 57 | return 0; 58 | } 59 | 60 | + (BOOL)canCacheResponse:(NSCachedURLResponse *)response request:(NSURLRequest *)request { 61 | NSURLRequest *httpRequest = request; 62 | if (!httpRequest) return false; 63 | 64 | NSHTTPURLResponse *httpResponse = nil; 65 | if ([response.response isKindOfClass:[NSHTTPURLResponse class]]) { 66 | httpResponse = (NSHTTPURLResponse *)response.response; 67 | } 68 | if (!httpResponse) return false; 69 | 70 | // HTTP status codes: https://tools.ietf.org/html/rfc7231#section-6.1 71 | switch (httpResponse.statusCode) { 72 | case 200: 73 | case 203: 74 | case 204: 75 | case 206: 76 | case 300: 77 | case 301: 78 | case 404: 79 | case 405: 80 | case 410: 81 | case 414: 82 | case 501: 83 | break; 84 | 85 | default: 86 | return false; 87 | } 88 | 89 | if (httpResponse.allHeaderFields[@"Vary"] != nil) { 90 | return false; 91 | } 92 | 93 | NSDate *now = [NSDate date]; 94 | NSDate *expirationStart; 95 | 96 | NSString *dateString = httpResponse.allHeaderFields[@"Date"]; 97 | if (dateString) { 98 | expirationStart = dateFromString(dateString); 99 | } else { 100 | // TODO: maybe date is null, return false 101 | // 暂时这么处理 102 | NSLog(@"--------------------the header field Date is null------------------"); 103 | return false; 104 | } 105 | 106 | if (httpResponse.allHeaderFields[@"WWW-Authenticate"] || httpResponse.allHeaderFields[@"Proxy-Authenticate"] || 107 | httpResponse.allHeaderFields[@"Authorization"] || httpResponse.allHeaderFields[@"Proxy-Authorization"]) { 108 | return false; 109 | } 110 | 111 | // HTTP Methods: https://tools.ietf.org/html/rfc7231#section-4.2.3 112 | if ([httpRequest.HTTPMethod isEqualToString:@"GET"]) { 113 | } else if ([httpRequest.HTTPMethod isEqualToString:@"HEAD"]) { 114 | if (response.data && response.data.length > 0) { 115 | return false; 116 | } 117 | } else { 118 | return false; 119 | } 120 | 121 | // Cache-Control: https://tools.ietf.org/html/rfc7234#section-5.2 122 | BOOL hasCacheControl = false; 123 | BOOL hasMaxAge = false; 124 | NSString *cacheControl = httpResponse.allHeaderFields[@"Cache-Control"]; 125 | if (cacheControl) { 126 | NSInteger maxAge = 0; 127 | NSInteger sharedMaxAge = 0; 128 | BOOL noCache = false; 129 | BOOL noStore = false; 130 | 131 | [self getCacheControlDeirectivesFromHeaderValue:cacheControl 132 | maxAge:&maxAge 133 | sharedMaxAge:&sharedMaxAge 134 | noCache:&noCache 135 | noStore:&noStore]; 136 | if (noCache || noStore) return false; 137 | 138 | if (maxAge > 0) { 139 | hasMaxAge = true; 140 | 141 | NSDate *expiration = [expirationStart dateByAddingTimeInterval:maxAge]; 142 | if ([now timeIntervalSince1970] >= [expiration timeIntervalSince1970]) { 143 | return false; 144 | } 145 | } 146 | 147 | if (sharedMaxAge) hasMaxAge = true; 148 | hasCacheControl = true; 149 | } 150 | 151 | NSString *pragma = httpResponse.allHeaderFields[@"Pragma"]; 152 | if (!hasCacheControl && pragma) { 153 | NSArray *components = [pragma componentsSeparatedByString:@","]; 154 | components = [components ym_map:^id _Nonnull(NSString *_Nonnull obj) { 155 | NSString *part = [obj stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 156 | part = [part lowercaseStringWithLocale:[NSLocale systemLocale]]; 157 | return part; 158 | }]; 159 | if ([components containsObject:@"no-cache"]) { 160 | return false; 161 | } 162 | } 163 | 164 | NSString *expires = httpResponse.allHeaderFields[@"Expires"]; 165 | if (!hasMaxAge && expires) { 166 | NSDate *expiration = dateFromString(expires); 167 | if (!expiration) return false; 168 | 169 | if ([now timeIntervalSince1970] >= [expiration timeIntervalSince1970]) { 170 | return false; 171 | } 172 | } 173 | 174 | if (!hasCacheControl) return false; 175 | 176 | return true; 177 | } 178 | + (void)getCacheControlDeirectivesFromHeaderValue:(NSString *)headerValue 179 | maxAge:(NSInteger *)maxAge 180 | sharedMaxAge:(NSInteger *)sharedMaxAge 181 | noCache:(BOOL *)noCache 182 | noStore:(BOOL *)noStore { 183 | NSArray *components = [headerValue componentsSeparatedByString:@","]; 184 | for (NSString *obj in components) { 185 | NSString *part = [obj stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 186 | part = [part lowercaseStringWithLocale:[NSLocale systemLocale]]; 187 | if ([part isEqualToString:@"no-cache"]) { 188 | *noCache = true; 189 | } else if ([part isEqualToString:@"no-store"]) { 190 | *noStore = true; 191 | } else if ([part containsString:@"max-age"]) { 192 | *maxAge = parseArgumentPart(part, @"max-age"); 193 | } else if ([part containsString:@"s-maxage"]) { 194 | *sharedMaxAge = parseArgumentPart(part, @"s-maxage"); 195 | } 196 | } 197 | } 198 | 199 | @end 200 | -------------------------------------------------------------------------------- /YMHTTP/core/YMURLSessionAuthenticationChallengeSender.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLSessionAuthenticationChallengeSender.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/16. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface YMURLSessionAuthenticationChallengeSender : NSObject 13 | 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /YMHTTP/core/YMURLSessionAuthenticationChallengeSender.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLSessionAuthenticationChallengeSender.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/16. 6 | // 7 | 8 | #import "YMURLSessionAuthenticationChallengeSender.h" 9 | 10 | @implementation YMURLSessionAuthenticationChallengeSender 11 | 12 | - (void)cancelAuthenticationChallenge:(nonnull NSURLAuthenticationChallenge *)challenge { 13 | } 14 | 15 | - (void)continueWithoutCredentialForAuthenticationChallenge:(nonnull NSURLAuthenticationChallenge *)challenge { 16 | } 17 | 18 | - (void)useCredential:(nonnull NSURLCredential *)credential 19 | forAuthenticationChallenge:(nonnull NSURLAuthenticationChallenge *)challenge { 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /YMHTTP/core/YMURLSessionTaskBehaviour.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLSessionTaskBehaviour.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/8. 6 | // 7 | 8 | #import 9 | 10 | typedef NS_ENUM(NSUInteger, YMURLSessionTaskBehaviourType) { 11 | /// The session has no delegate, or just a plain `URLSessionDelegate`. 12 | YMURLSessionTaskBehaviourTypeNoDelegate, 13 | /// The session has a delegate of type `URLSessionTaskDelegate` 14 | YMURLSessionTaskBehaviourTypeTaskDelegate, 15 | /// Default action for all events, except for completion. 16 | YMURLSessionTaskBehaviourTypeDataHandler, 17 | /// Default action for all events, except for completion. 18 | YMURLSessionTaskBehaviourTypeDownloadHandler 19 | }; 20 | 21 | NS_ASSUME_NONNULL_BEGIN 22 | 23 | typedef void (^YMDataTaskCompletion)(NSData *_Nullable data, 24 | NSHTTPURLResponse *_Nullable response, 25 | NSError *_Nullable error); 26 | typedef void (^YMDownloadTaskCompletion)(NSURL *_Nullable location, 27 | NSHTTPURLResponse *_Nullable response, 28 | NSError *_Nullable error); 29 | 30 | @interface YMURLSessionTaskBehaviour : NSObject 31 | 32 | @property (nonatomic, assign) YMURLSessionTaskBehaviourType type; 33 | @property (nonatomic, strong) YMDataTaskCompletion dataTaskCompeltion; 34 | @property (nonatomic, strong) YMDownloadTaskCompletion downloadCompletion; 35 | 36 | - (instancetype)init; 37 | - (instancetype)initWithDataTaskCompeltion:(YMDataTaskCompletion)dataTaskCompeltion; 38 | - (instancetype)initWithDownloadTaskCompeltion:(YMDownloadTaskCompletion)downloadTaskCompeltion; 39 | 40 | @end 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /YMHTTP/core/YMURLSessionTaskBehaviour.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLSessionTaskBehaviour.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/8. 6 | // 7 | 8 | #import "YMURLSessionTaskBehaviour.h" 9 | 10 | @implementation YMURLSessionTaskBehaviour 11 | 12 | - (instancetype)init { 13 | self = [super init]; 14 | if (self) { 15 | self.type = YMURLSessionTaskBehaviourTypeTaskDelegate; 16 | } 17 | return self; 18 | } 19 | 20 | - (instancetype)initWithDataTaskCompeltion:(YMDataTaskCompletion)dataTaskCompeltion { 21 | self = [super init]; 22 | if (self) { 23 | self.type = YMURLSessionTaskBehaviourTypeDataHandler; 24 | self.dataTaskCompeltion = dataTaskCompeltion; 25 | } 26 | return self; 27 | } 28 | 29 | - (instancetype)initWithDownloadTaskCompeltion:(YMDownloadTaskCompletion)downloadTaskCompeltion { 30 | self = [super init]; 31 | if (self) { 32 | self.type = YMURLSessionTaskBehaviourTypeDownloadHandler; 33 | self.downloadCompletion = downloadTaskCompeltion; 34 | } 35 | return self; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /YMHTTP/core/YMURLSessionTaskBody.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLSessionTaskBody.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/8. 6 | // 7 | 8 | #import 9 | 10 | typedef NS_ENUM(NSUInteger, YMURLSessionTaskBodyType) { 11 | YMURLSessionTaskBodyTypeNone, 12 | YMURLSessionTaskBodyTypeData, 13 | YMURLSessionTaskBodyTypeFile, 14 | YMURLSessionTaskBodyTypeStream, 15 | }; 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | @interface YMURLSessionTaskBody : NSObject 20 | 21 | @property (readonly, nonatomic, assign) YMURLSessionTaskBodyType type; 22 | @property (readonly, nonatomic, strong) NSData *data; 23 | @property (readonly, nonatomic, strong) NSURL *fileURL; 24 | @property (readonly, nonatomic, strong) NSInputStream *inputStream; 25 | 26 | - (instancetype)init; 27 | - (instancetype)initWithData:(NSData *)data; 28 | - (instancetype)initWithFileURL:(NSURL *)fileURL; 29 | - (instancetype)initWithInputStream:(NSInputStream *)InputStream; 30 | 31 | /// - Returns: The body length, or `nil` for no body (e.g. `GET` request). 32 | - (nullable NSNumber *)getBodyLengthWithError:(NSError **)error; 33 | 34 | @end 35 | 36 | NS_ASSUME_NONNULL_END 37 | -------------------------------------------------------------------------------- /YMHTTP/core/YMURLSessionTaskBody.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLSessionTaskBody.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/8. 6 | // 7 | 8 | #import "YMURLSessionTaskBody.h" 9 | 10 | @interface YMURLSessionTaskBody () 11 | 12 | @property (readwrite, nonatomic, assign) YMURLSessionTaskBodyType type; 13 | @property (readwrite, nonatomic, strong) NSData *data; 14 | @property (readwrite, nonatomic, strong) NSURL *fileURL; 15 | @property (readwrite, nonatomic, strong) NSInputStream *inputStream; 16 | 17 | @end 18 | 19 | @implementation YMURLSessionTaskBody 20 | 21 | - (instancetype)init { 22 | self = [super init]; 23 | if (self) { 24 | self.type = YMURLSessionTaskBodyTypeNone; 25 | } 26 | return self; 27 | } 28 | 29 | - (instancetype)initWithData:(NSData *)data { 30 | self = [super init]; 31 | if (self) { 32 | self.type = YMURLSessionTaskBodyTypeData; 33 | self.data = data; 34 | } 35 | return self; 36 | } 37 | 38 | - (instancetype)initWithFileURL:(NSURL *)fileURL { 39 | self = [super init]; 40 | if (self) { 41 | self.type = YMURLSessionTaskBodyTypeFile; 42 | self.fileURL = fileURL; 43 | } 44 | return self; 45 | } 46 | 47 | - (instancetype)initWithInputStream:(NSInputStream *)InputStream { 48 | self = [super init]; 49 | if (self) { 50 | self.type = YMURLSessionTaskBodyTypeStream; 51 | self.inputStream = InputStream; 52 | } 53 | return self; 54 | } 55 | 56 | - (NSNumber *)getBodyLengthWithError:(NSError **)error { 57 | switch (self.type) { 58 | case YMURLSessionTaskBodyTypeNone: 59 | return @(0); 60 | case YMURLSessionTaskBodyTypeData: 61 | return [NSNumber numberWithUnsignedInteger:[self.data length]]; 62 | case YMURLSessionTaskBodyTypeFile: { 63 | NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.fileURL.path 64 | error:error]; 65 | if (!error) { 66 | NSNumber *size = attributes[NSFileSize]; 67 | return size; 68 | } 69 | return nil; 70 | } 71 | case YMURLSessionTaskBodyTypeStream: 72 | return nil; 73 | } 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /YMHTTP/core/YMURLSessionTaskBodySource.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLSessionTaskBodySource.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/8. 6 | // 7 | 8 | #import 9 | 10 | typedef NS_ENUM(NSUInteger, YMBodySourceDataChunk) { 11 | YMBodySourceDataChunkData, 12 | YMBodySourceDataChunkDone, 13 | YMBodySourceDataChunkRetryLater, 14 | YMBodySourceDataChunkError 15 | }; 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | @protocol YMURLSessionTaskBodySource 20 | 21 | - (void)getNextChunkWithLength:(NSInteger)length 22 | completionHandler:(void (^)(YMBodySourceDataChunk chunk, NSData *_Nullable data))completionHandler; 23 | @end 24 | 25 | @interface YMBodyStreamSource : NSObject 26 | 27 | - (instancetype)initWithInputStream:(NSInputStream *)inputStream; 28 | 29 | @end 30 | 31 | @interface YMBodyDataSource : NSObject 32 | 33 | - (instancetype)initWithData:(NSData *)data; 34 | 35 | @end 36 | 37 | @interface YMBodyFileSource : NSObject 38 | 39 | - (instancetype)initWithFileURL:(NSURL *)fileURL 40 | workQueue:(dispatch_queue_t)workQueue 41 | dataAvailableHandler:(void (^)(void))dataAvailableHandler; 42 | 43 | @end 44 | 45 | NS_ASSUME_NONNULL_END 46 | -------------------------------------------------------------------------------- /YMHTTP/core/YMURLSessionTaskBodySource.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMURLSessionTaskBodySource.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/2/8. 6 | // 7 | 8 | #import "YMURLSessionTaskBodySource.h" 9 | #import "YMMacro.h" 10 | 11 | @interface YMBodyStreamSource () 12 | 13 | @property (nonatomic, strong) NSInputStream *inputStream; 14 | 15 | @end 16 | 17 | @implementation YMBodyStreamSource 18 | 19 | - (instancetype)initWithInputStream:(NSInputStream *)inputStream { 20 | self = [super init]; 21 | if (self) { 22 | self.inputStream = inputStream; 23 | if (self.inputStream.streamStatus == NSStreamStatusNotOpen) { 24 | [self.inputStream open]; 25 | } 26 | } 27 | return self; 28 | } 29 | 30 | - (void)getNextChunkWithLength:(NSInteger)length 31 | completionHandler:(void (^)(YMBodySourceDataChunk, NSData *_Nullable))completionHandler { 32 | if (!completionHandler) return; 33 | 34 | if (![self.inputStream hasBytesAvailable]) { 35 | completionHandler(YMBodySourceDataChunkDone, nil); 36 | return; 37 | } 38 | 39 | uint8_t buffer[length]; 40 | NSInteger readBytes = [self.inputStream read:buffer maxLength:length]; 41 | if (readBytes > 0) { 42 | NSData *data = [[NSData alloc] initWithBytes:buffer length:readBytes]; 43 | completionHandler(YMBodySourceDataChunkData, data); 44 | } else if (readBytes == 0) { 45 | completionHandler(YMBodySourceDataChunkDone, nil); 46 | } else { 47 | completionHandler(YMBodySourceDataChunkError, nil); 48 | } 49 | } 50 | 51 | @end 52 | 53 | @interface YMBodyDataSource () 54 | 55 | @property (nonatomic, strong) NSData *data; 56 | 57 | @end 58 | 59 | @implementation YMBodyDataSource 60 | 61 | - (instancetype)initWithData:(NSData *)data { 62 | self = [super init]; 63 | if (self) { 64 | self.data = data; 65 | } 66 | return self; 67 | } 68 | 69 | - (void)getNextChunkWithLength:(NSInteger)length 70 | completionHandler:(nonnull void (^)(YMBodySourceDataChunk, NSData *_Nullable))completionHandler { 71 | if (!completionHandler) return; 72 | NSUInteger remaining = self.data.length; 73 | if (remaining == 0) { 74 | completionHandler(YMBodySourceDataChunkDone, nil); 75 | } else if (remaining <= length) { 76 | NSData *r = [[NSData alloc] initWithData:self.data]; 77 | self.data = nil; 78 | completionHandler(YMBodySourceDataChunkData, r); 79 | } else { 80 | NSData *chunk = [self.data subdataWithRange:NSMakeRange(0, length)]; 81 | NSData *remainder = [self.data subdataWithRange:NSMakeRange(length - 1, self.data.length - length)]; 82 | self.data = remainder; 83 | completionHandler(YMBodySourceDataChunkData, chunk); 84 | } 85 | } 86 | 87 | @end 88 | 89 | typedef NS_ENUM(NSUInteger, YMBodyFileSourceChunk) { 90 | YMBodyFileSourceChunkEmpty, 91 | YMBodyFileSourceChunkErrorDetected, 92 | YMBodyFileSourceChunkData, 93 | YMBodyFileSourceChunkDone, 94 | }; 95 | 96 | @interface YMBodyFileSource () 97 | 98 | @property (nonatomic, strong) NSURL *fileURL; 99 | @property (nonatomic, strong) dispatch_io_t channel; 100 | @property (nonatomic, strong) dispatch_queue_t workQueue; 101 | @property (nonatomic, strong) void (^dataAvailableHandler)(void); 102 | @property (assign) BOOL hasActiveReadHandler; 103 | @property (nonatomic, assign) YMBodyFileSourceChunk availableChunk; 104 | @property (nonatomic, strong) dispatch_data_t availableData; 105 | 106 | @property (readonly, assign) NSInteger availableByteCount; 107 | @property (readonly, assign) NSInteger desiredBufferLength; 108 | 109 | @end 110 | 111 | @implementation YMBodyFileSource 112 | 113 | - (instancetype)initWithFileURL:(NSURL *)fileURL 114 | workQueue:(dispatch_queue_t)workQueue 115 | dataAvailableHandler:(void (^)(void))dataAvailableHandler { 116 | self = [super init]; 117 | if (self) { 118 | if (![fileURL isFileURL]) YM_FATALERROR(@"The body data URL must be a file URL."); 119 | 120 | self.fileURL = fileURL; 121 | self.workQueue = workQueue; 122 | self.dataAvailableHandler = dataAvailableHandler; 123 | 124 | const char *fileSystemRepresentation = fileURL.fileSystemRepresentation; 125 | if (fileSystemRepresentation != NULL) { 126 | int fd = open(fileSystemRepresentation, O_RDONLY); 127 | self.channel = dispatch_io_create(DISPATCH_IO_STREAM, 128 | fd, 129 | workQueue, 130 | ^(int error){ 131 | }); 132 | } else { 133 | YM_FATALERROR(@"Can't create DispatchIO channel"); 134 | } 135 | dispatch_io_set_high_water(_channel, CURL_MAX_WRITE_SIZE); 136 | } 137 | return self; 138 | } 139 | 140 | - (void)readNextChunk { 141 | // libcurl likes to use a buffer of size CURL_MAX_WRITE_SIZE, we'll 142 | // try to keep 3 x of that around in the `chunk` buffer. 143 | if (self.availableByteCount >= self.desiredBufferLength) return; 144 | 145 | if (self.hasActiveReadHandler) return; 146 | self.hasActiveReadHandler = true; 147 | 148 | NSInteger lengthToRead = self.desiredBufferLength - self.availableByteCount; 149 | dispatch_io_read( 150 | self.channel, 0, lengthToRead, self.workQueue, ^(bool done, dispatch_data_t _Nullable data, int error) { 151 | BOOL wasEmpty = self.availableByteCount == 0; 152 | 153 | self.hasActiveReadHandler = !done; 154 | 155 | if (done == true && error != 0) { 156 | self.availableChunk = YMBodyFileSourceChunkErrorDetected; 157 | } else if (done == true && error == 0) { 158 | if (dispatch_data_get_size(data) == 0) { 159 | [self appendData:data endOfFile:true]; 160 | } else { 161 | [self appendData:data endOfFile:false]; 162 | } 163 | } else if (done == false && error == 0) { 164 | [self appendData:data endOfFile:false]; 165 | } else { 166 | YM_FATALERROR(@"Invalid arguments to read(3) callback."); 167 | } 168 | 169 | if (wasEmpty && self.availableByteCount >= 0) { 170 | self.dataAvailableHandler(); 171 | } 172 | }); 173 | } 174 | 175 | - (void)appendData:(dispatch_data_t)data endOfFile:(BOOL)endOfFile { 176 | if (self.availableChunk == YMBodyFileSourceChunkEmpty) { 177 | self.availableData = data; 178 | if (endOfFile) { 179 | self.availableChunk = YMBodyFileSourceChunkDone; 180 | } else { 181 | self.availableChunk = YMBodyFileSourceChunkData; 182 | } 183 | return; 184 | } 185 | 186 | if (self.availableChunk == YMBodyFileSourceChunkData) { 187 | dispatch_data_t newData = dispatch_data_create_concat(self.availableData, data); 188 | self.availableData = newData; 189 | if (endOfFile) { 190 | _availableChunk = YMBodyFileSourceChunkDone; 191 | } else { 192 | self.availableChunk = YMBodyFileSourceChunkData; 193 | } 194 | return; 195 | } 196 | 197 | if (self.availableChunk == YMBodyFileSourceChunkDone) { 198 | YM_FATALERROR(@"Trying to append data, but end-of-file was already detected."); 199 | } 200 | } 201 | 202 | - (NSInteger)availableByteCount { 203 | switch (self.availableChunk) { 204 | case YMBodyFileSourceChunkEmpty: 205 | return 0; 206 | case YMBodyFileSourceChunkErrorDetected: 207 | return 0; 208 | case YMBodyFileSourceChunkData: 209 | return dispatch_data_get_size(_availableData); 210 | ; 211 | case YMBodyFileSourceChunkDone: { 212 | if (self.availableData == nil) { 213 | return 0; 214 | } else { 215 | return dispatch_data_get_size(_availableData); 216 | } 217 | } 218 | } 219 | } 220 | 221 | - (NSInteger)desiredBufferLength { 222 | return 3 * CURL_MAX_WRITE_SIZE; 223 | } 224 | 225 | - (void)getNextChunkWithLength:(NSInteger)length 226 | completionHandler:(void (^)(YMBodySourceDataChunk, NSData *_Nullable))completionHandler { 227 | switch (self.availableChunk) { 228 | case YMBodyFileSourceChunkEmpty: { 229 | [self readNextChunk]; 230 | completionHandler(YMBodySourceDataChunkRetryLater, nil); 231 | break; 232 | } 233 | case YMBodyFileSourceChunkErrorDetected: 234 | completionHandler(YMBodySourceDataChunkError, nil); 235 | break; 236 | case YMBodyFileSourceChunkData: { 237 | NSInteger l = dispatch_data_get_size(_availableData); 238 | NSInteger p = MIN(length, dispatch_data_get_size(_availableData)); 239 | 240 | dispatch_data_t head = dispatch_data_create_subrange(_availableData, 0, p); 241 | dispatch_data_t tail = dispatch_data_create_subrange(_availableData, p - 1, l - p); 242 | 243 | if (dispatch_data_get_size(tail) == 0) { 244 | self.availableChunk = YMBodyFileSourceChunkEmpty; 245 | } else { 246 | self.availableChunk = YMBodyFileSourceChunkData; 247 | self.availableData = tail; 248 | } 249 | [self readNextChunk]; 250 | 251 | size_t headCount = dispatch_data_get_size(head); 252 | if (headCount == 0) { 253 | completionHandler(YMBodySourceDataChunkRetryLater, nil); 254 | } else { 255 | char *buffer = (char *)malloc(sizeof(char) * headCount); 256 | [self copyBytesFromData:head toBuffer:buffer count:headCount]; 257 | completionHandler(YMBodySourceDataChunkData, [NSData dataWithBytesNoCopy:buffer length:p]); 258 | } 259 | break; 260 | } 261 | case YMBodyFileSourceChunkDone: { 262 | if (self.availableData == nil) { 263 | completionHandler(YMBodySourceDataChunkDone, nil); 264 | break; 265 | } 266 | 267 | NSInteger l = dispatch_data_get_size(self.availableData); 268 | NSInteger p = MIN(length, dispatch_data_get_size(self.availableData)); 269 | 270 | dispatch_data_t head = dispatch_data_create_subrange(self.availableData, 0, p); 271 | dispatch_data_t tail = dispatch_data_create_subrange(self.availableData, p - 1, l - p); 272 | 273 | if (dispatch_data_get_size(tail) == 0) { 274 | self.availableChunk = YMBodyFileSourceChunkDone; 275 | self.availableData = nil; 276 | } else { 277 | self.availableChunk = YMBodyFileSourceChunkDone; 278 | self.availableData = tail; 279 | } 280 | 281 | size_t headCount = dispatch_data_get_size(head); 282 | if (headCount == 0) { 283 | completionHandler(YMBodySourceDataChunkDone, nil); 284 | } else { 285 | char *buffer = (char *)malloc(sizeof(char) * headCount); 286 | [self copyBytesFromData:head toBuffer:buffer count:headCount]; 287 | completionHandler(YMBodySourceDataChunkData, [NSData dataWithBytesNoCopy:buffer length:p]); 288 | } 289 | break; 290 | } 291 | } 292 | } 293 | 294 | - (void)copyBytesFromData:(dispatch_data_t)data toBuffer:(char *)buffer count:(NSInteger)count { 295 | if (count == 0) return; 296 | 297 | __block NSInteger copiedCount = 0; 298 | NSInteger startIndex = 0; 299 | NSInteger endIndex = count - 1; 300 | 301 | dispatch_data_apply(data, 302 | ^bool(dispatch_data_t _Nonnull region, size_t offset, const void *_Nonnull ptr, size_t size) { 303 | if (offset >= endIndex) return false; // This region is after endIndex 304 | NSInteger copyOffset = 305 | startIndex > offset ? startIndex - offset : 0; // offset of first byte, in this region 306 | if (copyOffset >= size) return true; // This region is before startIndex 307 | NSInteger n = MIN(count - copiedCount, size - copyOffset); 308 | memcpy(buffer + copiedCount, ptr + copyOffset, n); 309 | copiedCount += n; 310 | return copiedCount < count; 311 | }); 312 | } 313 | 314 | @end 315 | -------------------------------------------------------------------------------- /YMHTTP/curl/YMEasyHandle.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMEasyHandle.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2019/12/31. 6 | // 7 | 8 | #import 9 | 10 | @class YMURLSessionConfiguration; 11 | @class YMURLSessionTask; 12 | @class YMTimeoutSource; 13 | 14 | typedef NS_ENUM(NSUInteger, YMEasyHandleAction) { 15 | YMEasyHandleActionAbort, 16 | YMEasyHandleActionProceed, 17 | YMEasyHandleActionPause, 18 | }; 19 | typedef NS_ENUM(NSUInteger, YMEasyHandleWriteBufferResult) { 20 | YMEasyHandleWriteBufferResultAbort, 21 | YMEasyHandleWriteBufferResultPause, 22 | YMEasyHandleWriteBufferResultBytes, 23 | }; 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | @protocol YMEasyHandleDelegate 28 | 29 | /// Handle data read from the network 30 | - (YMEasyHandleAction)didReceiveWithData:(NSData *)data; 31 | 32 | /// Handle header data read from the network 33 | - (YMEasyHandleAction)didReceiveWithHeaderData:(NSData *)data contentLength:(int64_t)contentLength; 34 | 35 | - (void)transferCompletedWithError:(NSError *)error; 36 | 37 | - (void)fillWriteBufferLength:(NSInteger)length 38 | result:(void (^)(YMEasyHandleWriteBufferResult result, NSInteger length, NSData *_Nullable data)) 39 | result; 40 | 41 | - (BOOL)seekInputStreamToPosition:(uint64_t)position; 42 | 43 | - (void)needTimeoutTimerToValue:(NSInteger)value; 44 | 45 | - (void)updateProgressMeterWithTotalBytesSent:(int64_t)totalBytesSent 46 | totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend 47 | totalBytesReceived:(int64_t)totalBytesReceived 48 | totalBytesExpectedToReceive:(int64_t)totalBytesExpectedToReceive; 49 | 50 | @end 51 | 52 | typedef void *YMURLSessionEasyHandle; 53 | 54 | @interface YMEasyHandle : NSObject 55 | 56 | @property (nonatomic, assign) YMURLSessionEasyHandle rawHandle; 57 | @property (nonatomic, assign) char *errorBuffer; 58 | @property (nullable, nonatomic, weak) id delegate; 59 | @property (nullable, nonatomic, strong) YMTimeoutSource *timeoutTimer; 60 | @property (nonatomic, strong) NSURL *URL; 61 | 62 | - (instancetype)initWithDelegate:(id)delegate; 63 | 64 | - (void)transferCompletedWithError:(nullable NSError *)error; 65 | 66 | - (int)urlErrorCodeWithEasyCode:(int)easyCode; 67 | 68 | - (void)setVerboseMode:(BOOL)flag; 69 | 70 | /// - SeeAlso: https://curl.haxx.se/libcurl/c/CFURLSessionOptionDEBUGFUNCTION.html 71 | - (void)setDebugOutput:(BOOL)flag task:(YMURLSessionTask *)task; 72 | 73 | - (void)setPassHeadersToDataStream:(BOOL)flag; 74 | 75 | /// Follow any Location: header that the server sends as part of a HTTP header in a 3xx response 76 | - (void)setFollowLocation:(BOOL)flag; 77 | 78 | /// Switch off the progress meter. It will also prevent the CFURLSessionOptionPROGRESSFUNCTION from getting called. 79 | - (void)setProgressMeterOff:(BOOL)flag; 80 | 81 | /// Skip all signal handling 82 | /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_NOSIGNAL.html 83 | - (void)setSkipAllSignalHandling:(BOOL)flag; 84 | 85 | /// Set error buffer for error messages 86 | /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_ERRORBUFFER.html 87 | - (void)setErrorBuffer:(nullable char *)buffer; 88 | 89 | /// Request failure on HTTP response >= 400 90 | - (void)setFailOnHTTPErrorCode:(BOOL)flag; 91 | 92 | /// URL to use in the request 93 | /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_URL.html 94 | - (void)setURL:(NSURL *)URL; 95 | 96 | - (void)setConnectToHost:(NSString *)host port:(NSInteger)port; 97 | 98 | - (void)setSessionConfig:(YMURLSessionConfiguration *)config; 99 | 100 | /// Set allowed protocols 101 | /// 102 | /// - Note: This has security implications. Not limiting this, someone could 103 | /// redirect a HTTP request into one of the many other protocols that libcurl 104 | /// supports. 105 | /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_PROTOCOLS.html 106 | /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_REDIR_PROTOCOLS.html 107 | - (void)setAllowedProtocolsToHTTPAndHTTPS; 108 | 109 | /// set preferred receive buffer size 110 | /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_BUFFERSIZE.html 111 | - (void)setPreferredReceiveBufferSize:(NSInteger)size; 112 | 113 | /// Set custom HTTP headers 114 | /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html 115 | - (void)setCustomHeaders:(NSArray *)headers; 116 | 117 | - (void)setAutomaticBodyDecompression:(BOOL)flag; 118 | 119 | /// Set request method 120 | /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_CUSTOMREQUEST.html 121 | - (void)setRequestMethod:(NSString *)method; 122 | 123 | /// Download request without body 124 | /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_NOBODY.html 125 | - (void)setNoBody:(BOOL)flag; 126 | 127 | /// Enable data upload 128 | /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_UPLOAD.html 129 | - (void)setUpload:(BOOL)flag; 130 | 131 | /// Set size of the request body to send 132 | /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_INFILESIZE_LARGE.html 133 | - (void)setRequestBodyLength:(int64_t)length; 134 | 135 | - (void)setTimeout:(NSInteger)timeout; 136 | 137 | - (void)setProxy; 138 | 139 | - (double)getTimeoutIntervalSpent; 140 | 141 | - (void)pauseReceive; 142 | - (void)unpauseReceive; 143 | 144 | - (void)pauseSend; 145 | - (void)unpauseSend; 146 | 147 | @end 148 | 149 | NS_ASSUME_NONNULL_END 150 | -------------------------------------------------------------------------------- /YMHTTP/curl/YMMacro.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMMacro.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/1/3. 6 | // 7 | 8 | #import 9 | #import "curl.h" 10 | 11 | #ifndef YMMacro_h 12 | #define YMMacro_h 13 | 14 | #ifndef YY_WEAKIFY 15 | #if DEBUG 16 | #if __has_feature(objc_arc) 17 | #define YY_WEAKIFY(object) \ 18 | autoreleasepool {} \ 19 | __weak __typeof__(object) weak##_##object = object; 20 | #else 21 | #define YY_WEAKIFY(object) \ 22 | autoreleasepool {} \ 23 | __block __typeof__(object) block##_##object = object; 24 | #endif 25 | #else 26 | #if __has_feature(objc_arc) 27 | #define YY_WEAKIFY(object) \ 28 | try { \ 29 | } @finally { \ 30 | } \ 31 | {} \ 32 | __weak __typeof__(object) weak##_##object = object; 33 | #else 34 | #define YY_WEAKIFY(object) \ 35 | try { \ 36 | } @finally { \ 37 | } \ 38 | {} \ 39 | __block __typeof__(object) block##_##object = object; 40 | #endif 41 | #endif 42 | #endif 43 | 44 | #ifndef YY_STRONGIFY 45 | #if DEBUG 46 | #if __has_feature(objc_arc) 47 | #define YY_STRONGIFY(object) \ 48 | autoreleasepool {} \ 49 | __typeof__(object) object = weak##_##object; 50 | #else 51 | #define YY_STRONGIFY(object) \ 52 | autoreleasepool {} \ 53 | __typeof__(object) object = block##_##object; 54 | #endif 55 | #else 56 | #if __has_feature(objc_arc) 57 | #define YY_STRONGIFY(object) \ 58 | try { \ 59 | } @finally { \ 60 | } \ 61 | __typeof__(object) object = weak##_##object; 62 | #else 63 | #define YY_STRONGIFY(object) \ 64 | try { \ 65 | } @finally { \ 66 | } \ 67 | __typeof__(object) object = block##_##object; 68 | #endif 69 | #endif 70 | #endif 71 | 72 | #define YM_DEFER \ 73 | EXT_KEYWORDIFY \ 74 | __strong ym_deferBlock_t ym_deferBlock_##__LINE__ __attribute__((cleanup(ym_deferFunc), unused)) = ^ 75 | 76 | #if defined(DEBUG) 77 | #define EXT_KEYWORDIFY \ 78 | autoreleasepool {} 79 | #else 80 | #define EXT_KEYWORDIFY \ 81 | try { \ 82 | } @catch (...) { \ 83 | } 84 | #endif 85 | typedef void (^ym_deferBlock_t)(void); 86 | NS_INLINE void ym_deferFunc(__strong ym_deferBlock_t *blockRef) { (*blockRef)(); } 87 | 88 | #ifndef YM_ECODE 89 | #define YM_ECODE(c) ym_handleEasyCode(c) 90 | #endif 91 | 92 | #ifndef YM_MCODE 93 | #define YM_MCODE(c) ym_handleMultiCode(c) 94 | #endif 95 | 96 | #ifndef YM_FATALERROR 97 | #define YM_FATALERROR(v) ym_throwException(@"ym_fatal error", v, nil) 98 | #endif 99 | 100 | NS_INLINE void ym_initializeLibcurl() { curl_global_init(CURL_GLOBAL_SSL); } 101 | 102 | NS_INLINE void ym_handleEasyCode(int code) { 103 | if (code == CURLE_OK) return; 104 | NSString *reason = [NSString stringWithFormat:@"An error occurred, CURLcode is %@", @(code)]; 105 | NSException *e = [NSException exceptionWithName:@"libcurl.easy" reason:reason userInfo:nil]; 106 | @throw e; 107 | } 108 | 109 | NS_INLINE void ym_throwException(NSString *name, NSString *reason, NSDictionary *userInfo) { 110 | if (!name) return; 111 | NSException *e = [NSException exceptionWithName:name reason:reason userInfo:userInfo]; 112 | @throw e; 113 | } 114 | 115 | NS_INLINE void ym_handleMultiCode(int code) { 116 | if (code == CURLM_OK) return; 117 | NSString *reason = [NSString stringWithFormat:@"An error occurred, CURLMcode is %@", @(code)]; 118 | NSException *e = [NSException exceptionWithName:@"libcurl.multi" reason:reason userInfo:nil]; 119 | @throw e; 120 | } 121 | 122 | NS_INLINE NSString *ym_getCurlVersion() { 123 | char *info = curl_version(); 124 | return [NSString stringWithUTF8String:info]; 125 | } 126 | 127 | #endif /* YMMacro_h */ 128 | -------------------------------------------------------------------------------- /YMHTTP/curl/YMMultiHandle.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMMultiHandle.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/1/3. 6 | // 7 | 8 | #import 9 | #import "curl.h" 10 | 11 | @class YMURLSessionConfiguration; 12 | @class YMEasyHandle; 13 | 14 | typedef void *YMURLSessionMultiHandle; 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | @interface YMMultiHandle : NSObject 19 | 20 | @property (nonatomic, assign) YMURLSessionMultiHandle rawHandle; 21 | 22 | - (instancetype)initWithConfiguration:(YMURLSessionConfiguration *)configuration WorkQueue:(dispatch_queue_t)workQueque; 23 | - (void)addHandle:(YMEasyHandle *)handle; 24 | - (void)removeHandle:(YMEasyHandle *)handle; 25 | - (void)updateTimeoutTimerToValue:(NSInteger)value; 26 | 27 | @end 28 | 29 | typedef NS_ENUM(NSUInteger, YMSocketRegisterActionType) { 30 | YMSocketRegisterActionTypeNone = 0, 31 | YMSocketRegisterActionTypeRegisterRead, 32 | YMSocketRegisterActionTypeRegisterWrite, 33 | YMSocketRegisterActionTypeRegisterReadAndWrite, 34 | YMSocketRegisterActionTypeUnregister 35 | 36 | }; 37 | 38 | @interface YMSocketRegisterAction : NSObject 39 | 40 | - (instancetype)initWithRawValue:(int)rawValue; 41 | 42 | @property (readonly, nonatomic, assign) YMSocketRegisterActionType type; 43 | @property (readonly, nonatomic, assign) BOOL needsReadSource; 44 | @property (readonly, nonatomic, assign) BOOL needsWriteSource; 45 | @property (readonly, nonatomic, assign) BOOL needsSource; 46 | 47 | @end 48 | 49 | @interface YMSocketSources : NSObject 50 | 51 | @property (nonatomic, strong, nullable) dispatch_source_t readSource; 52 | @property (nonatomic, strong, nullable) dispatch_source_t writeSource; 53 | 54 | - (void)createSourcesWithAction:(YMSocketRegisterAction *)action 55 | socket:(curl_socket_t)socket 56 | queue:(dispatch_queue_t)queue 57 | handler:(dispatch_block_t)handler; 58 | - (void)createReadSourceWithSocket:(curl_socket_t)socket 59 | queue:(dispatch_queue_t)queue 60 | handler:(dispatch_block_t)handler; 61 | - (void)createWriteSourceWithSocket:(curl_socket_t)socket 62 | queue:(dispatch_queue_t)queue 63 | handler:(dispatch_block_t)handler; 64 | 65 | + (instancetype)from:(void *)socketSourcePtr; 66 | 67 | @end 68 | 69 | NS_ASSUME_NONNULL_END 70 | -------------------------------------------------------------------------------- /YMHTTP/curl/YMMultiHandle.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMMultiHandle.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/1/3. 6 | // 7 | 8 | #import "YMMultiHandle.h" 9 | #import "YMEasyHandle.h" 10 | #import "YMMacro.h" 11 | #import "YMTimeoutSource.h" 12 | #import "YMURLSessionConfiguration.h" 13 | #import "curl.h" 14 | 15 | @interface YMMultiHandle () 16 | 17 | @property (nonatomic, strong) NSMutableArray *easyHandles; 18 | @property (nonatomic, strong) dispatch_queue_t queue; 19 | @property (nonatomic, strong) YMTimeoutSource *timeoutSource; 20 | 21 | @end 22 | 23 | @implementation YMMultiHandle 24 | 25 | - (instancetype)initWithConfiguration:(YMURLSessionConfiguration *)configuration 26 | WorkQueue:(dispatch_queue_t)workQueque { 27 | self = [super init]; 28 | if (self) { 29 | self.rawHandle = curl_multi_init(); 30 | self.easyHandles = [[NSMutableArray alloc] init]; 31 | self.queue = dispatch_queue_create_with_target("YMMutilHandle.isolation", DISPATCH_QUEUE_SERIAL, workQueque); 32 | [self setupCallbacks]; 33 | [self configureWithConfiguration:configuration]; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)dealloc { 39 | [self.easyHandles enumerateObjectsUsingBlock:^(YMEasyHandle *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { 40 | curl_multi_remove_handle(_rawHandle, obj.rawHandle); 41 | }]; 42 | curl_multi_cleanup(_rawHandle); 43 | } 44 | 45 | - (void)configureWithConfiguration:(YMURLSessionConfiguration *)configuration { 46 | YM_ECODE(curl_multi_setopt(_rawHandle, CURLMOPT_MAX_HOST_CONNECTIONS, configuration.HTTPMaximumConnectionsPerHost)); 47 | YM_ECODE(curl_multi_setopt(_rawHandle, CURLMOPT_PIPELINING, configuration.HTTPShouldUsePipelining ? 3 : 2)); 48 | } 49 | 50 | - (void)setupCallbacks { 51 | // socket 52 | YM_ECODE(curl_multi_setopt(_rawHandle, CURLMOPT_SOCKETDATA, (__bridge void *)self)); 53 | YM_ECODE(curl_multi_setopt(_rawHandle, CURLMOPT_SOCKETFUNCTION, _curlm_socket_function)); 54 | 55 | // timeout 56 | YM_ECODE(curl_multi_setopt(_rawHandle, CURLMOPT_TIMERDATA, (__bridge void *)self)); 57 | YM_ECODE(curl_multi_setopt(_rawHandle, CURLMOPT_TIMERFUNCTION, _curlm_timer_function)); 58 | } 59 | 60 | - (int32_t)registerWithSocket:(curl_socket_t)socket what:(int)what socketSourcePtr:(void *)socketSourcePtr { 61 | // We get this callback whenever we need to register or unregister a 62 | // given socket with libdispatch. 63 | // The `action` / `what` defines if we should register or unregister 64 | // that we're interested in read and/or write readiness. We will do so 65 | // through libdispatch (DispatchSource) and store the source(s) inside 66 | // a `SocketSources` which we in turn store inside libcurl's multi handle 67 | // by means of curl_multi_assign() -- we retain the object fist. 68 | 69 | YMSocketRegisterAction *action = [[YMSocketRegisterAction alloc] initWithRawValue:what]; 70 | YMSocketSources *socketSources = [YMSocketSources from:socketSourcePtr]; 71 | 72 | if (socketSources == nil && action.needsSource) { 73 | YMSocketSources *s = [[YMSocketSources alloc] init]; 74 | void *sp = (__bridge_retained void *)s; 75 | curl_multi_assign(_rawHandle, socket, sp); 76 | socketSources = s; 77 | } else if (socketSources != nil && action.type == YMSocketRegisterActionTypeUnregister) { 78 | YMSocketSources *s = (__bridge_transfer YMSocketSources *)socketSourcePtr; 79 | s = nil; 80 | } 81 | if (socketSources) { 82 | __weak typeof(self) _wself = self; 83 | [socketSources createSourcesWithAction:action 84 | socket:socket 85 | queue:_queue 86 | handler:^{ 87 | [_wself performActionForSocket:socket]; 88 | }]; 89 | } 90 | 91 | return 0; 92 | } 93 | 94 | #pragma mark - Public Methods 95 | 96 | - (void)addHandle:(YMEasyHandle *)handle { 97 | // If this is the first handle being added, we need to `kick` the 98 | // underlying multi handle by calling `timeoutTimerFired` as 99 | // described in 100 | // . 101 | // That will initiate the registration for timeout timer and socket 102 | // readiness. 103 | BOOL needsTimeout = false; 104 | if ([_easyHandles count] == 0) needsTimeout = YES; 105 | [self.easyHandles addObject:handle]; 106 | 107 | YM_MCODE(curl_multi_add_handle(_rawHandle, handle.rawHandle)); 108 | if (needsTimeout) [self timeoutTimerFired]; 109 | } 110 | 111 | - (void)removeHandle:(YMEasyHandle *)handle { 112 | NSUInteger idx = [self.easyHandles 113 | indexOfObjectPassingTest:^BOOL(YMEasyHandle *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { 114 | if (obj.rawHandle == handle.rawHandle) { 115 | *stop = YES; 116 | return YES; 117 | } 118 | return NO; 119 | }]; 120 | 121 | if (idx == NSNotFound) { 122 | YM_FATALERROR(@"Handle not in list."); 123 | return; 124 | } 125 | 126 | [self.easyHandles removeObjectAtIndex:idx]; 127 | YM_MCODE(curl_multi_remove_handle(_rawHandle, handle.rawHandle)); 128 | } 129 | 130 | #pragma mark - libcurl callback 131 | 132 | NS_INLINE YMMultiHandle *from(void *userdata) { 133 | if (!userdata) return nil; 134 | return (__bridge YMMultiHandle *)userdata; 135 | } 136 | 137 | int _curlm_socket_function( 138 | YMURLSessionEasyHandle easyHandle, curl_socket_t socket, int what, void *userdata, void *socketptr) { 139 | YMMultiHandle *handle = from(userdata); 140 | if (!handle) { 141 | YM_FATALERROR(nil); 142 | } 143 | 144 | return [handle registerWithSocket:socket what:what socketSourcePtr:socketptr]; 145 | } 146 | 147 | int _curlm_timer_function(YMURLSessionEasyHandle easyHandle, int timeout, void *userdata) { 148 | YMMultiHandle *handle = from(userdata); 149 | if (!handle) { 150 | YM_FATALERROR(nil); 151 | } 152 | [handle updateTimeoutTimerToValue:timeout]; 153 | return 0; 154 | } 155 | 156 | #pragma mark - Primate Methods 157 | 158 | - (void)performActionForSocket:(int)socket { 159 | [self readAndWriteAvailableDataOnSocket:socket]; 160 | } 161 | 162 | - (void)timeoutTimerFired { 163 | [self readAndWriteAvailableDataOnSocket:CURL_SOCKET_TIMEOUT]; 164 | } 165 | 166 | - (void)readAndWriteAvailableDataOnSocket:(int)socket { 167 | int runningHandlesCount = 0; 168 | YM_MCODE(curl_multi_socket_action(_rawHandle, socket, 0, &runningHandlesCount)); 169 | [self readMessages]; 170 | } 171 | 172 | /// Check the status of all individual transfers. 173 | /// 174 | /// libcurl refers to this as “read multi stack informationals”. 175 | /// Check for transfers that completed. 176 | - (void)readMessages { 177 | while (true) { 178 | int count = 0; 179 | CURLMsg *msg = mutilHandleInfoRead(_rawHandle, &count); 180 | if (msg == NULL || !msg->easy_handle) break; 181 | YMURLSessionEasyHandle easyHandle = msg->easy_handle; 182 | int code = msg->data.result; 183 | [self completedTransferForEasyHandle:easyHandle easyCode:code]; 184 | } 185 | } 186 | 187 | - (void)completedTransferForEasyHandle:(YMURLSessionEasyHandle)handle easyCode:(int)easyCode { 188 | NSUInteger idx = [self.easyHandles 189 | indexOfObjectPassingTest:^BOOL(YMEasyHandle *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { 190 | if (obj.rawHandle == handle) { 191 | *stop = YES; 192 | return YES; 193 | } 194 | return NO; 195 | }]; 196 | 197 | if (idx == NSNotFound) { 198 | YM_FATALERROR(@"Transfer completed for easy handle, but it is not in the list of added handles."); 199 | return; 200 | } 201 | YMEasyHandle *easyHandle = self.easyHandles[idx]; 202 | NSError *err = nil; 203 | int errCode = [easyHandle urlErrorCodeWithEasyCode:easyCode]; 204 | if (errCode != 0) { 205 | NSString *d = nil; 206 | if (easyHandle.errorBuffer[0] == 0) { 207 | const char *description = curl_easy_strerror(errCode); 208 | d = [[NSString alloc] initWithCString:description encoding:NSUTF8StringEncoding]; 209 | } else { 210 | d = [[NSString alloc] initWithCString:easyHandle.errorBuffer encoding:NSUTF8StringEncoding]; 211 | } 212 | err = [NSError errorWithDomain:NSURLErrorDomain code:errCode userInfo:@{NSLocalizedDescriptionKey : d}]; 213 | } 214 | [easyHandle transferCompletedWithError:err]; 215 | } 216 | 217 | CURLMsg *mutilHandleInfoRead(YMURLSessionMultiHandle handle, int *msgs_in_queue) { 218 | CURLMsg *msg = curl_multi_info_read(handle, msgs_in_queue); 219 | 220 | if (msg == NULL) return NULL; 221 | if (msg->msg != CURLMSG_DONE) return NULL; 222 | 223 | return msg; 224 | } 225 | 226 | - (void)updateTimeoutTimerToValue:(NSInteger)value { 227 | // A timeout_ms value of -1 passed to this callback means you should delete the timer. All other values are valid 228 | // expire times in number of milliseconds. 229 | if (value == -1) 230 | self.timeoutSource = nil; 231 | else if (value == 0) { 232 | self.timeoutSource = nil; 233 | dispatch_async(self.queue, ^{ 234 | [self timeoutTimerFired]; 235 | }); 236 | } else { 237 | if (self.timeoutSource == nil || self.timeoutSource.milliseconds != value) { 238 | __weak typeof(self) _wself = self; 239 | self.timeoutSource = [[YMTimeoutSource alloc] initWithQueue:self.queue 240 | milliseconds:value 241 | handler:^{ 242 | [_wself timeoutTimerFired]; 243 | }]; 244 | } 245 | } 246 | } 247 | 248 | @end 249 | 250 | @interface YMSocketRegisterAction () 251 | 252 | @property (readwrite, nonatomic, assign) YMSocketRegisterActionType type; 253 | 254 | @end 255 | 256 | @implementation YMSocketRegisterAction 257 | 258 | - (instancetype)initWithRawValue:(int)rawValue { 259 | self = [super init]; 260 | if (self) { 261 | switch (rawValue) { 262 | case CURL_POLL_NONE: 263 | self.type = YMSocketRegisterActionTypeNone; 264 | break; 265 | case CURL_POLL_IN: 266 | self.type = YMSocketRegisterActionTypeRegisterRead; 267 | break; 268 | case CURL_POLL_OUT: 269 | self.type = YMSocketRegisterActionTypeRegisterWrite; 270 | break; 271 | case CURL_POLL_INOUT: 272 | self.type = YMSocketRegisterActionTypeRegisterReadAndWrite; 273 | break; 274 | case CURL_POLL_REMOVE: 275 | self.type = YMSocketRegisterActionTypeUnregister; 276 | break; 277 | default: 278 | YM_FATALERROR(@"Invalid CURL_POLL value."); 279 | break; 280 | } 281 | } 282 | return self; 283 | } 284 | 285 | /// Should a libdispatch source be registered for **read** readiness? 286 | - (BOOL)needsReadSource { 287 | switch (self.type) { 288 | case YMSocketRegisterActionTypeNone: 289 | return false; 290 | case YMSocketRegisterActionTypeRegisterRead: 291 | return true; 292 | case YMSocketRegisterActionTypeRegisterWrite: 293 | return false; 294 | case YMSocketRegisterActionTypeRegisterReadAndWrite: 295 | return true; 296 | case YMSocketRegisterActionTypeUnregister: 297 | return false; 298 | } 299 | } 300 | 301 | /// Should a libdispatch source be registered for **write** readiness? 302 | - (BOOL)needsWriteSource { 303 | switch (self.type) { 304 | case YMSocketRegisterActionTypeNone: 305 | return false; 306 | case YMSocketRegisterActionTypeRegisterRead: 307 | return false; 308 | case YMSocketRegisterActionTypeRegisterWrite: 309 | return true; 310 | case YMSocketRegisterActionTypeRegisterReadAndWrite: 311 | return true; 312 | case YMSocketRegisterActionTypeUnregister: 313 | return false; 314 | } 315 | } 316 | 317 | /// Should either a **read** or a **write** readiness libdispatch source be 318 | /// registered? 319 | - (BOOL)needsSource { 320 | return self.needsReadSource || self.needsWriteSource; 321 | } 322 | 323 | @end 324 | 325 | @implementation YMSocketSources 326 | 327 | - (void)createSourcesWithAction:(YMSocketRegisterAction *)action 328 | socket:(curl_socket_t)socket 329 | queue:(dispatch_queue_t)queue 330 | handler:(dispatch_block_t)handler { 331 | if (action.needsReadSource) { 332 | [self createReadSourceWithSocket:socket queue:queue handler:handler]; 333 | } 334 | 335 | if (action.needsWriteSource) { 336 | [self createWriteSourceWithSocket:socket queue:queue handler:handler]; 337 | } 338 | } 339 | 340 | - (void)createReadSourceWithSocket:(curl_socket_t)socket 341 | queue:(dispatch_queue_t)queue 342 | handler:(dispatch_block_t)handler { 343 | if (_readSource) return; 344 | 345 | dispatch_source_t s = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket, 0, queue); 346 | dispatch_source_set_event_handler(s, handler); 347 | _readSource = s; 348 | dispatch_resume(s); 349 | } 350 | 351 | - (void)createWriteSourceWithSocket:(curl_socket_t)socket 352 | queue:(dispatch_queue_t)queue 353 | handler:(dispatch_block_t)handler { 354 | if (_writeSource) return; 355 | 356 | dispatch_source_t s = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket, 0, queue); 357 | dispatch_source_set_event_handler(s, handler); 358 | _writeSource = s; 359 | dispatch_resume(s); 360 | } 361 | 362 | - (void)tearDown { 363 | if (_readSource) { 364 | dispatch_source_cancel(_readSource); 365 | } 366 | _readSource = nil; 367 | 368 | if (_writeSource) { 369 | dispatch_source_cancel(_writeSource); 370 | } 371 | _writeSource = nil; 372 | } 373 | 374 | + (instancetype)from:(void *)socketSourcePtr { 375 | if (!socketSourcePtr) return nil; 376 | return (__bridge YMSocketSources *)socketSourcePtr; 377 | } 378 | 379 | @end 380 | -------------------------------------------------------------------------------- /YMHTTP/curl/YMTimeoutSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMTimeoutSource.h 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/1/2. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface YMTimeoutSource : NSObject 13 | 14 | @property (nonatomic, strong) dispatch_source_t rawSource; 15 | @property (nonatomic, assign) NSInteger milliseconds; 16 | @property (nonatomic, strong) dispatch_queue_t queue; 17 | @property (nonatomic, strong) dispatch_block_t handler; 18 | 19 | - (instancetype)initWithQueue:(dispatch_queue_t)queue 20 | milliseconds:(NSInteger)milliseconds 21 | handler:(dispatch_block_t)handler; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /YMHTTP/curl/YMTimeoutSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // YMTimeoutSource.m 3 | // YMHTTP 4 | // 5 | // Created by zymxxxs on 2020/1/2. 6 | // 7 | #import "YMTimeoutSource.h" 8 | 9 | @implementation YMTimeoutSource 10 | 11 | - (instancetype)initWithQueue:(dispatch_queue_t)queue 12 | milliseconds:(NSInteger)milliseconds 13 | handler:(dispatch_block_t)handler { 14 | self = [super init]; 15 | if (self) { 16 | self.queue = queue; 17 | self.handler = handler; 18 | self.milliseconds = milliseconds; 19 | self.rawSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue); 20 | 21 | uint64_t delay = MAX(1, milliseconds - 1); 22 | 23 | dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_MSEC); 24 | 25 | dispatch_source_set_timer(self.rawSource, 26 | start, 27 | delay * NSEC_PER_MSEC, 28 | self.milliseconds == 1 ? 1 * NSEC_PER_USEC : 1 * NSEC_PER_MSEC); 29 | dispatch_source_set_event_handler(self.rawSource, self.handler); 30 | dispatch_resume(self.rawSource); 31 | } 32 | return self; 33 | } 34 | 35 | - (void)dealloc { 36 | dispatch_source_cancel(self.rawSource); 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /YMHTTP/libcurl/curlver.h: -------------------------------------------------------------------------------- 1 | #ifndef __CURL_CURLVER_H 2 | #define __CURL_CURLVER_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) 1998 - 2019, Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.haxx.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | ***************************************************************************/ 24 | 25 | /* This header file contains nothing but libcurl version info, generated by 26 | a script at release-time. This was made its own header file in 7.11.2 */ 27 | 28 | /* This is the global package copyright */ 29 | #define LIBCURL_COPYRIGHT "1996 - 2019 Daniel Stenberg, ." 30 | 31 | /* This is the version number of the libcurl package from which this header 32 | file origins: */ 33 | #define LIBCURL_VERSION "7.64.1-DEV" 34 | 35 | /* The numeric version number is also available "in parts" by using these 36 | defines: */ 37 | #define LIBCURL_VERSION_MAJOR 7 38 | #define LIBCURL_VERSION_MINOR 64 39 | #define LIBCURL_VERSION_PATCH 1 40 | 41 | /* This is the numeric version of the libcurl version number, meant for easier 42 | parsing and comparions by programs. The LIBCURL_VERSION_NUM define will 43 | always follow this syntax: 44 | 45 | 0xXXYYZZ 46 | 47 | Where XX, YY and ZZ are the main version, release and patch numbers in 48 | hexadecimal (using 8 bits each). All three numbers are always represented 49 | using two digits. 1.2 would appear as "0x010200" while version 9.11.7 50 | appears as "0x090b07". 51 | 52 | This 6-digit (24 bits) hexadecimal number does not show pre-release number, 53 | and it is always a greater number in a more recent release. It makes 54 | comparisons with greater than and less than work. 55 | 56 | Note: This define is the full hex number and _does not_ use the 57 | CURL_VERSION_BITS() macro since curl's own configure script greps for it 58 | and needs it to contain the full number. 59 | */ 60 | #define LIBCURL_VERSION_NUM 0x074001 61 | 62 | /* 63 | * This is the date and time when the full source package was created. The 64 | * timestamp is not stored in git, as the timestamp is properly set in the 65 | * tarballs by the maketgz script. 66 | * 67 | * The format of the date follows this template: 68 | * 69 | * "2007-11-23" 70 | */ 71 | #define LIBCURL_TIMESTAMP "[unreleased]" 72 | 73 | #define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|z) 74 | #define CURL_AT_LEAST_VERSION(x,y,z) \ 75 | (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z)) 76 | 77 | #endif /* __CURL_CURLVER_H */ 78 | -------------------------------------------------------------------------------- /YMHTTP/libcurl/easy.h: -------------------------------------------------------------------------------- 1 | #ifndef __CURL_EASY_H 2 | #define __CURL_EASY_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.haxx.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | ***************************************************************************/ 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | CURL_EXTERN CURL *curl_easy_init(void); 29 | CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...); 30 | CURL_EXTERN CURLcode curl_easy_perform(CURL *curl); 31 | CURL_EXTERN void curl_easy_cleanup(CURL *curl); 32 | 33 | /* 34 | * NAME curl_easy_getinfo() 35 | * 36 | * DESCRIPTION 37 | * 38 | * Request internal information from the curl session with this function. The 39 | * third argument MUST be a pointer to a long, a pointer to a char * or a 40 | * pointer to a double (as the documentation describes elsewhere). The data 41 | * pointed to will be filled in accordingly and can be relied upon only if the 42 | * function returns CURLE_OK. This function is intended to get used *AFTER* a 43 | * performed transfer, all results from this function are undefined until the 44 | * transfer is completed. 45 | */ 46 | CURL_EXTERN CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...); 47 | 48 | 49 | /* 50 | * NAME curl_easy_duphandle() 51 | * 52 | * DESCRIPTION 53 | * 54 | * Creates a new curl session handle with the same options set for the handle 55 | * passed in. Duplicating a handle could only be a matter of cloning data and 56 | * options, internal state info and things like persistent connections cannot 57 | * be transferred. It is useful in multithreaded applications when you can run 58 | * curl_easy_duphandle() for each new thread to avoid a series of identical 59 | * curl_easy_setopt() invokes in every thread. 60 | */ 61 | CURL_EXTERN CURL *curl_easy_duphandle(CURL *curl); 62 | 63 | /* 64 | * NAME curl_easy_reset() 65 | * 66 | * DESCRIPTION 67 | * 68 | * Re-initializes a CURL handle to the default values. This puts back the 69 | * handle to the same state as it was in when it was just created. 70 | * 71 | * It does keep: live connections, the Session ID cache, the DNS cache and the 72 | * cookies. 73 | */ 74 | CURL_EXTERN void curl_easy_reset(CURL *curl); 75 | 76 | /* 77 | * NAME curl_easy_recv() 78 | * 79 | * DESCRIPTION 80 | * 81 | * Receives data from the connected socket. Use after successful 82 | * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. 83 | */ 84 | CURL_EXTERN CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, 85 | size_t *n); 86 | 87 | /* 88 | * NAME curl_easy_send() 89 | * 90 | * DESCRIPTION 91 | * 92 | * Sends data over the connected socket. Use after successful 93 | * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. 94 | */ 95 | CURL_EXTERN CURLcode curl_easy_send(CURL *curl, const void *buffer, 96 | size_t buflen, size_t *n); 97 | 98 | 99 | /* 100 | * NAME curl_easy_upkeep() 101 | * 102 | * DESCRIPTION 103 | * 104 | * Performs connection upkeep for the given session handle. 105 | */ 106 | CURL_EXTERN CURLcode curl_easy_upkeep(CURL *curl); 107 | 108 | #ifdef __cplusplus 109 | } 110 | #endif 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /YMHTTP/libcurl/libcurl.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zymxxxs/YMHTTP/baeb6f2cb1e51b31aad8bcc75247b63a8dac8994/YMHTTP/libcurl/libcurl.a -------------------------------------------------------------------------------- /YMHTTP/libcurl/mprintf.h: -------------------------------------------------------------------------------- 1 | #ifndef __CURL_MPRINTF_H 2 | #define __CURL_MPRINTF_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.haxx.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | ***************************************************************************/ 24 | 25 | #include 26 | #include /* needed for FILE */ 27 | #include "curl.h" /* for CURL_EXTERN */ 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | CURL_EXTERN int curl_mprintf(const char *format, ...); 34 | CURL_EXTERN int curl_mfprintf(FILE *fd, const char *format, ...); 35 | CURL_EXTERN int curl_msprintf(char *buffer, const char *format, ...); 36 | CURL_EXTERN int curl_msnprintf(char *buffer, size_t maxlength, 37 | const char *format, ...); 38 | CURL_EXTERN int curl_mvprintf(const char *format, va_list args); 39 | CURL_EXTERN int curl_mvfprintf(FILE *fd, const char *format, va_list args); 40 | CURL_EXTERN int curl_mvsprintf(char *buffer, const char *format, va_list args); 41 | CURL_EXTERN int curl_mvsnprintf(char *buffer, size_t maxlength, 42 | const char *format, va_list args); 43 | CURL_EXTERN char *curl_maprintf(const char *format, ...); 44 | CURL_EXTERN char *curl_mvaprintf(const char *format, va_list args); 45 | 46 | #ifdef __cplusplus 47 | } 48 | #endif 49 | 50 | #endif /* __CURL_MPRINTF_H */ 51 | -------------------------------------------------------------------------------- /YMHTTP/libcurl/multi.h: -------------------------------------------------------------------------------- 1 | #ifndef __CURL_MULTI_H 2 | #define __CURL_MULTI_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.haxx.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | ***************************************************************************/ 24 | /* 25 | This is an "external" header file. Don't give away any internals here! 26 | 27 | GOALS 28 | 29 | o Enable a "pull" interface. The application that uses libcurl decides where 30 | and when to ask libcurl to get/send data. 31 | 32 | o Enable multiple simultaneous transfers in the same thread without making it 33 | complicated for the application. 34 | 35 | o Enable the application to select() on its own file descriptors and curl's 36 | file descriptors simultaneous easily. 37 | 38 | */ 39 | 40 | /* 41 | * This header file should not really need to include "curl.h" since curl.h 42 | * itself includes this file and we expect user applications to do #include 43 | * without the need for especially including multi.h. 44 | * 45 | * For some reason we added this include here at one point, and rather than to 46 | * break existing (wrongly written) libcurl applications, we leave it as-is 47 | * but with this warning attached. 48 | */ 49 | #include "curl.h" 50 | 51 | #ifdef __cplusplus 52 | extern "C" { 53 | #endif 54 | 55 | #if defined(BUILDING_LIBCURL) || defined(CURL_STRICTER) 56 | typedef struct Curl_multi CURLM; 57 | #else 58 | typedef void CURLM; 59 | #endif 60 | 61 | typedef enum { 62 | CURLM_CALL_MULTI_PERFORM = -1, /* please call curl_multi_perform() or 63 | curl_multi_socket*() soon */ 64 | CURLM_OK, 65 | CURLM_BAD_HANDLE, /* the passed-in handle is not a valid CURLM handle */ 66 | CURLM_BAD_EASY_HANDLE, /* an easy handle was not good/valid */ 67 | CURLM_OUT_OF_MEMORY, /* if you ever get this, you're in deep sh*t */ 68 | CURLM_INTERNAL_ERROR, /* this is a libcurl bug */ 69 | CURLM_BAD_SOCKET, /* the passed in socket argument did not match */ 70 | CURLM_UNKNOWN_OPTION, /* curl_multi_setopt() with unsupported option */ 71 | CURLM_ADDED_ALREADY, /* an easy handle already added to a multi handle was 72 | attempted to get added - again */ 73 | CURLM_RECURSIVE_API_CALL, /* an api function was called from inside a 74 | callback */ 75 | CURLM_LAST 76 | } CURLMcode; 77 | 78 | /* just to make code nicer when using curl_multi_socket() you can now check 79 | for CURLM_CALL_MULTI_SOCKET too in the same style it works for 80 | curl_multi_perform() and CURLM_CALL_MULTI_PERFORM */ 81 | #define CURLM_CALL_MULTI_SOCKET CURLM_CALL_MULTI_PERFORM 82 | 83 | /* bitmask bits for CURLMOPT_PIPELINING */ 84 | #define CURLPIPE_NOTHING 0L 85 | #define CURLPIPE_HTTP1 1L 86 | #define CURLPIPE_MULTIPLEX 2L 87 | 88 | typedef enum { 89 | CURLMSG_NONE, /* first, not used */ 90 | CURLMSG_DONE, /* This easy handle has completed. 'result' contains 91 | the CURLcode of the transfer */ 92 | CURLMSG_LAST /* last, not used */ 93 | } CURLMSG; 94 | 95 | struct CURLMsg { 96 | CURLMSG msg; /* what this message means */ 97 | CURL *easy_handle; /* the handle it concerns */ 98 | union { 99 | void *whatever; /* message-specific data */ 100 | CURLcode result; /* return code for transfer */ 101 | } data; 102 | }; 103 | typedef struct CURLMsg CURLMsg; 104 | 105 | /* Based on poll(2) structure and values. 106 | * We don't use pollfd and POLL* constants explicitly 107 | * to cover platforms without poll(). */ 108 | #define CURL_WAIT_POLLIN 0x0001 109 | #define CURL_WAIT_POLLPRI 0x0002 110 | #define CURL_WAIT_POLLOUT 0x0004 111 | 112 | struct curl_waitfd { 113 | curl_socket_t fd; 114 | short events; 115 | short revents; /* not supported yet */ 116 | }; 117 | 118 | /* 119 | * Name: curl_multi_init() 120 | * 121 | * Desc: inititalize multi-style curl usage 122 | * 123 | * Returns: a new CURLM handle to use in all 'curl_multi' functions. 124 | */ 125 | CURL_EXTERN CURLM *curl_multi_init(void); 126 | 127 | /* 128 | * Name: curl_multi_add_handle() 129 | * 130 | * Desc: add a standard curl handle to the multi stack 131 | * 132 | * Returns: CURLMcode type, general multi error code. 133 | */ 134 | CURL_EXTERN CURLMcode curl_multi_add_handle(CURLM *multi_handle, 135 | CURL *curl_handle); 136 | 137 | /* 138 | * Name: curl_multi_remove_handle() 139 | * 140 | * Desc: removes a curl handle from the multi stack again 141 | * 142 | * Returns: CURLMcode type, general multi error code. 143 | */ 144 | CURL_EXTERN CURLMcode curl_multi_remove_handle(CURLM *multi_handle, 145 | CURL *curl_handle); 146 | 147 | /* 148 | * Name: curl_multi_fdset() 149 | * 150 | * Desc: Ask curl for its fd_set sets. The app can use these to select() or 151 | * poll() on. We want curl_multi_perform() called as soon as one of 152 | * them are ready. 153 | * 154 | * Returns: CURLMcode type, general multi error code. 155 | */ 156 | CURL_EXTERN CURLMcode curl_multi_fdset(CURLM *multi_handle, 157 | fd_set *read_fd_set, 158 | fd_set *write_fd_set, 159 | fd_set *exc_fd_set, 160 | int *max_fd); 161 | 162 | /* 163 | * Name: curl_multi_wait() 164 | * 165 | * Desc: Poll on all fds within a CURLM set as well as any 166 | * additional fds passed to the function. 167 | * 168 | * Returns: CURLMcode type, general multi error code. 169 | */ 170 | CURL_EXTERN CURLMcode curl_multi_wait(CURLM *multi_handle, 171 | struct curl_waitfd extra_fds[], 172 | unsigned int extra_nfds, 173 | int timeout_ms, 174 | int *ret); 175 | 176 | /* 177 | * Name: curl_multi_perform() 178 | * 179 | * Desc: When the app thinks there's data available for curl it calls this 180 | * function to read/write whatever there is right now. This returns 181 | * as soon as the reads and writes are done. This function does not 182 | * require that there actually is data available for reading or that 183 | * data can be written, it can be called just in case. It returns 184 | * the number of handles that still transfer data in the second 185 | * argument's integer-pointer. 186 | * 187 | * Returns: CURLMcode type, general multi error code. *NOTE* that this only 188 | * returns errors etc regarding the whole multi stack. There might 189 | * still have occurred problems on individual transfers even when 190 | * this returns OK. 191 | */ 192 | CURL_EXTERN CURLMcode curl_multi_perform(CURLM *multi_handle, 193 | int *running_handles); 194 | 195 | /* 196 | * Name: curl_multi_cleanup() 197 | * 198 | * Desc: Cleans up and removes a whole multi stack. It does not free or 199 | * touch any individual easy handles in any way. We need to define 200 | * in what state those handles will be if this function is called 201 | * in the middle of a transfer. 202 | * 203 | * Returns: CURLMcode type, general multi error code. 204 | */ 205 | CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle); 206 | 207 | /* 208 | * Name: curl_multi_info_read() 209 | * 210 | * Desc: Ask the multi handle if there's any messages/informationals from 211 | * the individual transfers. Messages include informationals such as 212 | * error code from the transfer or just the fact that a transfer is 213 | * completed. More details on these should be written down as well. 214 | * 215 | * Repeated calls to this function will return a new struct each 216 | * time, until a special "end of msgs" struct is returned as a signal 217 | * that there is no more to get at this point. 218 | * 219 | * The data the returned pointer points to will not survive calling 220 | * curl_multi_cleanup(). 221 | * 222 | * The 'CURLMsg' struct is meant to be very simple and only contain 223 | * very basic information. If more involved information is wanted, 224 | * we will provide the particular "transfer handle" in that struct 225 | * and that should/could/would be used in subsequent 226 | * curl_easy_getinfo() calls (or similar). The point being that we 227 | * must never expose complex structs to applications, as then we'll 228 | * undoubtably get backwards compatibility problems in the future. 229 | * 230 | * Returns: A pointer to a filled-in struct, or NULL if it failed or ran out 231 | * of structs. It also writes the number of messages left in the 232 | * queue (after this read) in the integer the second argument points 233 | * to. 234 | */ 235 | CURL_EXTERN CURLMsg *curl_multi_info_read(CURLM *multi_handle, 236 | int *msgs_in_queue); 237 | 238 | /* 239 | * Name: curl_multi_strerror() 240 | * 241 | * Desc: The curl_multi_strerror function may be used to turn a CURLMcode 242 | * value into the equivalent human readable error string. This is 243 | * useful for printing meaningful error messages. 244 | * 245 | * Returns: A pointer to a zero-terminated error message. 246 | */ 247 | CURL_EXTERN const char *curl_multi_strerror(CURLMcode); 248 | 249 | /* 250 | * Name: curl_multi_socket() and 251 | * curl_multi_socket_all() 252 | * 253 | * Desc: An alternative version of curl_multi_perform() that allows the 254 | * application to pass in one of the file descriptors that have been 255 | * detected to have "action" on them and let libcurl perform. 256 | * See man page for details. 257 | */ 258 | #define CURL_POLL_NONE 0 259 | #define CURL_POLL_IN 1 260 | #define CURL_POLL_OUT 2 261 | #define CURL_POLL_INOUT 3 262 | #define CURL_POLL_REMOVE 4 263 | 264 | #define CURL_SOCKET_TIMEOUT CURL_SOCKET_BAD 265 | 266 | #define CURL_CSELECT_IN 0x01 267 | #define CURL_CSELECT_OUT 0x02 268 | #define CURL_CSELECT_ERR 0x04 269 | 270 | typedef int (*curl_socket_callback)(CURL *easy, /* easy handle */ 271 | curl_socket_t s, /* socket */ 272 | int what, /* see above */ 273 | void *userp, /* private callback 274 | pointer */ 275 | void *socketp); /* private socket 276 | pointer */ 277 | /* 278 | * Name: curl_multi_timer_callback 279 | * 280 | * Desc: Called by libcurl whenever the library detects a change in the 281 | * maximum number of milliseconds the app is allowed to wait before 282 | * curl_multi_socket() or curl_multi_perform() must be called 283 | * (to allow libcurl's timed events to take place). 284 | * 285 | * Returns: The callback should return zero. 286 | */ 287 | typedef int (*curl_multi_timer_callback)(CURLM *multi, /* multi handle */ 288 | long timeout_ms, /* see above */ 289 | void *userp); /* private callback 290 | pointer */ 291 | 292 | CURL_EXTERN CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s, 293 | int *running_handles); 294 | 295 | CURL_EXTERN CURLMcode curl_multi_socket_action(CURLM *multi_handle, 296 | curl_socket_t s, 297 | int ev_bitmask, 298 | int *running_handles); 299 | 300 | CURL_EXTERN CURLMcode curl_multi_socket_all(CURLM *multi_handle, 301 | int *running_handles); 302 | 303 | #ifndef CURL_ALLOW_OLD_MULTI_SOCKET 304 | /* This macro below was added in 7.16.3 to push users who recompile to use 305 | the new curl_multi_socket_action() instead of the old curl_multi_socket() 306 | */ 307 | #define curl_multi_socket(x,y,z) curl_multi_socket_action(x,y,0,z) 308 | #endif 309 | 310 | /* 311 | * Name: curl_multi_timeout() 312 | * 313 | * Desc: Returns the maximum number of milliseconds the app is allowed to 314 | * wait before curl_multi_socket() or curl_multi_perform() must be 315 | * called (to allow libcurl's timed events to take place). 316 | * 317 | * Returns: CURLM error code. 318 | */ 319 | CURL_EXTERN CURLMcode curl_multi_timeout(CURLM *multi_handle, 320 | long *milliseconds); 321 | 322 | #undef CINIT /* re-using the same name as in curl.h */ 323 | 324 | #ifdef CURL_ISOCPP 325 | #define CINIT(name,type,num) CURLMOPT_ ## name = CURLOPTTYPE_ ## type + num 326 | #else 327 | /* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */ 328 | #define LONG CURLOPTTYPE_LONG 329 | #define OBJECTPOINT CURLOPTTYPE_OBJECTPOINT 330 | #define FUNCTIONPOINT CURLOPTTYPE_FUNCTIONPOINT 331 | #define OFF_T CURLOPTTYPE_OFF_T 332 | #define CINIT(name,type,number) CURLMOPT_/**/name = type + number 333 | #endif 334 | 335 | typedef enum { 336 | /* This is the socket callback function pointer */ 337 | CINIT(SOCKETFUNCTION, FUNCTIONPOINT, 1), 338 | 339 | /* This is the argument passed to the socket callback */ 340 | CINIT(SOCKETDATA, OBJECTPOINT, 2), 341 | 342 | /* set to 1 to enable pipelining for this multi handle */ 343 | CINIT(PIPELINING, LONG, 3), 344 | 345 | /* This is the timer callback function pointer */ 346 | CINIT(TIMERFUNCTION, FUNCTIONPOINT, 4), 347 | 348 | /* This is the argument passed to the timer callback */ 349 | CINIT(TIMERDATA, OBJECTPOINT, 5), 350 | 351 | /* maximum number of entries in the connection cache */ 352 | CINIT(MAXCONNECTS, LONG, 6), 353 | 354 | /* maximum number of (pipelining) connections to one host */ 355 | CINIT(MAX_HOST_CONNECTIONS, LONG, 7), 356 | 357 | /* maximum number of requests in a pipeline */ 358 | CINIT(MAX_PIPELINE_LENGTH, LONG, 8), 359 | 360 | /* a connection with a content-length longer than this 361 | will not be considered for pipelining */ 362 | CINIT(CONTENT_LENGTH_PENALTY_SIZE, OFF_T, 9), 363 | 364 | /* a connection with a chunk length longer than this 365 | will not be considered for pipelining */ 366 | CINIT(CHUNK_LENGTH_PENALTY_SIZE, OFF_T, 10), 367 | 368 | /* a list of site names(+port) that are blacklisted from 369 | pipelining */ 370 | CINIT(PIPELINING_SITE_BL, OBJECTPOINT, 11), 371 | 372 | /* a list of server types that are blacklisted from 373 | pipelining */ 374 | CINIT(PIPELINING_SERVER_BL, OBJECTPOINT, 12), 375 | 376 | /* maximum number of open connections in total */ 377 | CINIT(MAX_TOTAL_CONNECTIONS, LONG, 13), 378 | 379 | /* This is the server push callback function pointer */ 380 | CINIT(PUSHFUNCTION, FUNCTIONPOINT, 14), 381 | 382 | /* This is the argument passed to the server push callback */ 383 | CINIT(PUSHDATA, OBJECTPOINT, 15), 384 | 385 | CURLMOPT_LASTENTRY /* the last unused */ 386 | } CURLMoption; 387 | 388 | 389 | /* 390 | * Name: curl_multi_setopt() 391 | * 392 | * Desc: Sets options for the multi handle. 393 | * 394 | * Returns: CURLM error code. 395 | */ 396 | CURL_EXTERN CURLMcode curl_multi_setopt(CURLM *multi_handle, 397 | CURLMoption option, ...); 398 | 399 | 400 | /* 401 | * Name: curl_multi_assign() 402 | * 403 | * Desc: This function sets an association in the multi handle between the 404 | * given socket and a private pointer of the application. This is 405 | * (only) useful for curl_multi_socket uses. 406 | * 407 | * Returns: CURLM error code. 408 | */ 409 | CURL_EXTERN CURLMcode curl_multi_assign(CURLM *multi_handle, 410 | curl_socket_t sockfd, void *sockp); 411 | 412 | 413 | /* 414 | * Name: curl_push_callback 415 | * 416 | * Desc: This callback gets called when a new stream is being pushed by the 417 | * server. It approves or denies the new stream. 418 | * 419 | * Returns: CURL_PUSH_OK or CURL_PUSH_DENY. 420 | */ 421 | #define CURL_PUSH_OK 0 422 | #define CURL_PUSH_DENY 1 423 | 424 | struct curl_pushheaders; /* forward declaration only */ 425 | 426 | CURL_EXTERN char *curl_pushheader_bynum(struct curl_pushheaders *h, 427 | size_t num); 428 | CURL_EXTERN char *curl_pushheader_byname(struct curl_pushheaders *h, 429 | const char *name); 430 | 431 | typedef int (*curl_push_callback)(CURL *parent, 432 | CURL *easy, 433 | size_t num_headers, 434 | struct curl_pushheaders *headers, 435 | void *userp); 436 | 437 | #ifdef __cplusplus 438 | } /* end of extern "C" */ 439 | #endif 440 | 441 | #endif 442 | -------------------------------------------------------------------------------- /YMHTTP/libcurl/stdcheaders.h: -------------------------------------------------------------------------------- 1 | #ifndef __STDC_HEADERS_H 2 | #define __STDC_HEADERS_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.haxx.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | ***************************************************************************/ 24 | 25 | #include 26 | 27 | size_t fread(void *, size_t, size_t, FILE *); 28 | size_t fwrite(const void *, size_t, size_t, FILE *); 29 | 30 | int strcasecmp(const char *, const char *); 31 | int strncasecmp(const char *, const char *, size_t); 32 | 33 | #endif /* __STDC_HEADERS_H */ 34 | -------------------------------------------------------------------------------- /YMHTTP/libcurl/urlapi.h: -------------------------------------------------------------------------------- 1 | #ifndef __CURL_URLAPI_H 2 | #define __CURL_URLAPI_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) 2018 - 2019, Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.haxx.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | ***************************************************************************/ 24 | 25 | #include "curl.h" 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | /* the error codes for the URL API */ 32 | typedef enum { 33 | CURLUE_OK, 34 | CURLUE_BAD_HANDLE, /* 1 */ 35 | CURLUE_BAD_PARTPOINTER, /* 2 */ 36 | CURLUE_MALFORMED_INPUT, /* 3 */ 37 | CURLUE_BAD_PORT_NUMBER, /* 4 */ 38 | CURLUE_UNSUPPORTED_SCHEME, /* 5 */ 39 | CURLUE_URLDECODE, /* 6 */ 40 | CURLUE_OUT_OF_MEMORY, /* 7 */ 41 | CURLUE_USER_NOT_ALLOWED, /* 8 */ 42 | CURLUE_UNKNOWN_PART, /* 9 */ 43 | CURLUE_NO_SCHEME, /* 10 */ 44 | CURLUE_NO_USER, /* 11 */ 45 | CURLUE_NO_PASSWORD, /* 12 */ 46 | CURLUE_NO_OPTIONS, /* 13 */ 47 | CURLUE_NO_HOST, /* 14 */ 48 | CURLUE_NO_PORT, /* 15 */ 49 | CURLUE_NO_QUERY, /* 16 */ 50 | CURLUE_NO_FRAGMENT /* 17 */ 51 | } CURLUcode; 52 | 53 | typedef enum { 54 | CURLUPART_URL, 55 | CURLUPART_SCHEME, 56 | CURLUPART_USER, 57 | CURLUPART_PASSWORD, 58 | CURLUPART_OPTIONS, 59 | CURLUPART_HOST, 60 | CURLUPART_PORT, 61 | CURLUPART_PATH, 62 | CURLUPART_QUERY, 63 | CURLUPART_FRAGMENT 64 | } CURLUPart; 65 | 66 | #define CURLU_DEFAULT_PORT (1<<0) /* return default port number */ 67 | #define CURLU_NO_DEFAULT_PORT (1<<1) /* act as if no port number was set, 68 | if the port number matches the 69 | default for the scheme */ 70 | #define CURLU_DEFAULT_SCHEME (1<<2) /* return default scheme if 71 | missing */ 72 | #define CURLU_NON_SUPPORT_SCHEME (1<<3) /* allow non-supported scheme */ 73 | #define CURLU_PATH_AS_IS (1<<4) /* leave dot sequences */ 74 | #define CURLU_DISALLOW_USER (1<<5) /* no user+password allowed */ 75 | #define CURLU_URLDECODE (1<<6) /* URL decode on get */ 76 | #define CURLU_URLENCODE (1<<7) /* URL encode on set */ 77 | #define CURLU_APPENDQUERY (1<<8) /* append a form style part */ 78 | #define CURLU_GUESS_SCHEME (1<<9) /* legacy curl-style guessing */ 79 | 80 | typedef struct Curl_URL CURLU; 81 | 82 | /* 83 | * curl_url() creates a new CURLU handle and returns a pointer to it. 84 | * Must be freed with curl_url_cleanup(). 85 | */ 86 | CURL_EXTERN CURLU *curl_url(void); 87 | 88 | /* 89 | * curl_url_cleanup() frees the CURLU handle and related resources used for 90 | * the URL parsing. It will not free strings previously returned with the URL 91 | * API. 92 | */ 93 | CURL_EXTERN void curl_url_cleanup(CURLU *handle); 94 | 95 | /* 96 | * curl_url_dup() duplicates a CURLU handle and returns a new copy. The new 97 | * handle must also be freed with curl_url_cleanup(). 98 | */ 99 | CURL_EXTERN CURLU *curl_url_dup(CURLU *in); 100 | 101 | /* 102 | * curl_url_get() extracts a specific part of the URL from a CURLU 103 | * handle. Returns error code. The returned pointer MUST be freed with 104 | * curl_free() afterwards. 105 | */ 106 | CURL_EXTERN CURLUcode curl_url_get(CURLU *handle, CURLUPart what, 107 | char **part, unsigned int flags); 108 | 109 | /* 110 | * curl_url_set() sets a specific part of the URL in a CURLU handle. Returns 111 | * error code. The passed in string will be copied. Passing a NULL instead of 112 | * a part string, clears that part. 113 | */ 114 | CURL_EXTERN CURLUcode curl_url_set(CURLU *handle, CURLUPart what, 115 | const char *part, unsigned int flags); 116 | 117 | 118 | #ifdef __cplusplus 119 | } /* end of extern "C" */ 120 | #endif 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /code_format.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import subprocess 3 | 4 | glob_paths = ['YMHTTP/core/*.[h, m]', 5 | 'YMHTTP/curl/*.[h, m]', 6 | 'YMHTTP/*.[h, m]', 7 | 'Example/Tests/**/*.[h, m]'] 8 | format_files = [] 9 | 10 | for glob_path in glob_paths: 11 | files = glob.glob(glob_path, recursive=True) 12 | format_files.extend(files) 13 | 14 | for format_file in format_files: 15 | subprocess.call('clang-format -style=file -i ' + format_file, shell=True) 16 | 17 | 18 | print('code format complete!!') 19 | --------------------------------------------------------------------------------