diff --git a/YTKNetwork/YTKBaseRequest.h b/YTKNetwork/YTKBaseRequest.h index 8e59bc1..b404505 100644 --- a/YTKNetwork/YTKBaseRequest.h +++ b/YTKNetwork/YTKBaseRequest.h @@ -25,6 +25,13 @@ NS_ASSUME_NONNULL_BEGIN +FOUNDATION_EXPORT NSString *const YTKRequestValidationErrorDomain; + +NS_ENUM(NSInteger) { + YTKRequestValidationErrorInvalidStatusCode = -8, + YTKRequestValidationErrorInvalidJSONFormat = -9, +}; + /// HTTP Request method. typedef NS_ENUM(NSInteger, YTKRequestMethod) { YTKRequestMethodGET = 0, @@ -261,15 +268,24 @@ typedef void(^YTKRequestCompletionBlock)(__kindof YTKBaseRequest *request); /// Called on the main thread when request failed. - (void)requestFailedFilter; -/// The URL of request. +/// The baseURL of request. This should only contain the host part of URL, e.g., http://www.example.com. +/// See also `requestUrl` +- (NSString *)baseUrl; + +/// The URL path of request. This should only contain the path part of URL, e.g., /v1/user. See alse `baseUrl`. +/// +/// @discussion This will be concated with `baseUrl` using [NSURL URLWithString:relativeToURL]. +/// Because of this, it is recommended that the usage should stick to rules stated above. +/// Otherwise the result URL may not be correctly formed. See also `URLString:relativeToURL` +/// for more information. +/// +/// Additionaly, if `requestUrl` itself is a valid URL, it will be used as the result URL and +/// `baseUrl` will be ignored. - (NSString *)requestUrl; /// Optional CDN URL for request. - (NSString *)cdnUrl; -/// The baseURL of request. -- (NSString *)baseUrl; - /// Requset timeout interval. Default is 60s. /// /// @discussion When using `resumableDownloadPath`(NSURLSessionDownloadTask), the session seems to completely ignore diff --git a/YTKNetwork/YTKBaseRequest.m b/YTKNetwork/YTKBaseRequest.m index 27bacb4..cc0933c 100644 --- a/YTKNetwork/YTKBaseRequest.m +++ b/YTKNetwork/YTKBaseRequest.m @@ -31,6 +31,8 @@ #import "AFNetworking.h" #endif +NSString *const YTKRequestValidationErrorDomain = @"com.yuantiku.request.validation"; + @interface YTKBaseRequest () @property (nonatomic, strong, readwrite) NSURLSessionTask *requestTask; diff --git a/YTKNetwork/YTKNetworkAgent.m b/YTKNetwork/YTKNetworkAgent.m index 239a63c..7afbe5e 100644 --- a/YTKNetwork/YTKNetworkAgent.m +++ b/YTKNetwork/YTKNetworkAgent.m @@ -98,7 +98,9 @@ - (AFXMLParserResponseSerializer *)xmlParserResponseSerialzier { - (NSString *)buildRequestUrl:(YTKBaseRequest *)request { NSString *detailUrl = [request requestUrl]; - if ([detailUrl hasPrefix:@"http"]) { + NSURL *temp = [NSURL URLWithString:detailUrl]; + // If detailUrl is valid URL + if (temp && temp.host && temp.scheme) { return detailUrl; } // Filter URL if needed @@ -131,12 +133,7 @@ - (NSString *)buildRequestUrl:(YTKBaseRequest *)request { return [NSURL URLWithString:detailUrl relativeToURL:url].absoluteString; } -- (void)addRequest:(YTKBaseRequest *)request { - YTKRequestMethod method = [request requestMethod]; - NSString *url = [self buildRequestUrl:request]; - id param = request.requestArgument; - AFConstructingBlock constructingBlock = [request constructingBodyBlock]; - +- (AFHTTPRequestSerializer *)requestSerializerForRequest:(YTKBaseRequest *)request { AFHTTPRequestSerializer *requestSerializer = nil; if (request.requestSerializerType == YTKRequestSerializerTypeHTTP) { requestSerializer = [AFHTTPRequestSerializer serializer]; @@ -159,15 +156,42 @@ - (void)addRequest:(YTKBaseRequest *)request { if (headerFieldValueDictionary != nil) { for (NSString *httpHeaderField in headerFieldValueDictionary.allKeys) { NSString *value = headerFieldValueDictionary[httpHeaderField]; - if ([httpHeaderField isKindOfClass:[NSString class]] && [value isKindOfClass:[NSString class]]) { - [requestSerializer setValue:value forHTTPHeaderField:httpHeaderField]; + [requestSerializer setValue:value forHTTPHeaderField:httpHeaderField]; + } + } + return requestSerializer; +} + +- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error { + YTKRequestMethod method = [request requestMethod]; + NSString *url = [self buildRequestUrl:request]; + id param = request.requestArgument; + AFConstructingBlock constructingBlock = [request constructingBodyBlock]; + AFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request]; + + switch (method) { + case YTKRequestMethodGET: + if (request.resumableDownloadPath) { + return [self downloadTaskWithDownloadPath:request.resumableDownloadPath requestSerializer:requestSerializer URLString:url parameters:param progress:request.resumableDownloadProgressBlock error:error]; } else { - YTKLog(@"Error, class of key/value in headerFieldValueDictionary should be NSString."); + return [self dataTaskWithHTTPMethod:@"GET" requestSerializer:requestSerializer URLString:url parameters:param error:error]; } - } + case YTKRequestMethodPOST: + return [self dataTaskWithHTTPMethod:@"POST" requestSerializer:requestSerializer URLString:url parameters:param constructingBodyWithBlock:constructingBlock error:error]; + case YTKRequestMethodHEAD: + return [self dataTaskWithHTTPMethod:@"HEAD" requestSerializer:requestSerializer URLString:url parameters:param error:error]; + case YTKRequestMethodPUT: + return [self dataTaskWithHTTPMethod:@"PUT" requestSerializer:requestSerializer URLString:url parameters:param error:error]; + case YTKRequestMethodDELETE: + return [self dataTaskWithHTTPMethod:@"DELETE" requestSerializer:requestSerializer URLString:url parameters:param error:error]; + case YTKRequestMethodPATCH: + return [self dataTaskWithHTTPMethod:@"PATCH" requestSerializer:requestSerializer URLString:url parameters:param error:error]; } +} + +- (void)addRequest:(YTKBaseRequest *)request { + NSError * __autoreleasing requestSerializationError = nil; - // If api builds custom url request NSURLRequest *customUrlRequest= [request buildCustomUrlRequest]; if (customUrlRequest) { __block NSURLSessionDataTask *dataTask = nil; @@ -176,101 +200,14 @@ - (void)addRequest:(YTKBaseRequest *)request { }]; request.requestTask = dataTask; } else { - if (method == YTKRequestMethodGET) { - if (request.resumableDownloadPath) { - // add parameters to URL; - NSString *filteredUrl = [YTKNetworkUtils urlStringWithOriginUrlString:url appendParameters:param]; - NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:filteredUrl]]; - urlRequest.timeoutInterval = [request requestTimeoutInterval]; - urlRequest.allowsCellularAccess = [request allowsCellularAccess]; - - NSString *downloadTargetPath; - BOOL isDirectory; - if(![[NSFileManager defaultManager] fileExistsAtPath:request.resumableDownloadPath isDirectory:&isDirectory]) { - isDirectory = NO; - } - // If targetPath is a directory, use the file name we got from the urlRequest. - // Make sure downloadTargetPath is always a file, not directory. - if (isDirectory) { - NSString *fileName = [urlRequest.URL lastPathComponent]; - downloadTargetPath = [NSString pathWithComponents:@[request.resumableDownloadPath, fileName]]; - } else { - downloadTargetPath = request.resumableDownloadPath; - } - - BOOL resumeDataFileExists = [[NSFileManager defaultManager] fileExistsAtPath:[self incompleteDownloadTempPathForDownloadRequest:request].path]; - NSData *data = [NSData dataWithContentsOfURL:[self incompleteDownloadTempPathForDownloadRequest:request]]; - BOOL resumeDataIsValid = [YTKNetworkUtils validateResumeData:data]; - - BOOL canBeResumed = resumeDataFileExists && resumeDataIsValid; - BOOL resumeSucceeded = NO; - __block NSURLSessionDownloadTask *downloadTask = nil; - // Try to resume with resumeData. - // Even though we try to validate the resumeData, this may still fail and raise excecption. - if (canBeResumed) { - @try { - downloadTask = [_manager downloadTaskWithResumeData:data progress:request.resumableDownloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) { - return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO]; - } completionHandler: - ^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) { - [self handleRequestResult:downloadTask responseObject:filePath error:error]; - }]; - resumeSucceeded = YES; - } @catch (NSException *exception) { - YTKLog(@"Resume download failed, reason = %@", exception.reason); - resumeSucceeded = NO; - } - } - if (!resumeSucceeded) { - downloadTask = [_manager downloadTaskWithRequest:urlRequest progress:request.resumableDownloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) { - return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO]; - } completionHandler: - ^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) { - [self handleRequestResult:downloadTask responseObject:filePath error:error]; - }]; - } - request.requestTask = downloadTask; - } else { - request.requestTask = [self dataTaskWithHTTPMethod:@"GET" requestSerializer:requestSerializer URLString:url parameters:param]; - } - } else if (method == YTKRequestMethodPOST) { - if (constructingBlock != nil) { - NSError *serializationError = nil; - NSMutableURLRequest *urlRequest = [requestSerializer multipartFormRequestWithMethod:@"POST" URLString:url parameters:param constructingBodyWithBlock:constructingBlock error:&serializationError]; - urlRequest.timeoutInterval = [request requestTimeoutInterval]; - urlRequest.allowsCellularAccess = [request allowsCellularAccess]; - - if (serializationError) { - dispatch_async(_manager.completionQueue ?: dispatch_get_main_queue(), ^{ - [self handleRequestResult:nil responseObject:nil error:serializationError]; - }); - } else { - __block NSURLSessionDataTask *dataTask = nil; - dataTask = [_manager dataTaskWithRequest:urlRequest completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { - [self handleRequestResult:dataTask responseObject:responseObject error:error]; - }]; - request.requestTask = dataTask; - } - } else { - request.requestTask = [self dataTaskWithHTTPMethod:@"POST" requestSerializer:requestSerializer URLString:url parameters:param]; - } - } else if (method == YTKRequestMethodHEAD) { - request.requestTask = [self dataTaskWithHTTPMethod:@"HEAD" requestSerializer:requestSerializer URLString:url parameters:param]; - } else if (method == YTKRequestMethodPUT) { - request.requestTask = [self dataTaskWithHTTPMethod:@"PUT" requestSerializer:requestSerializer URLString:url parameters:param]; - } else if (method == YTKRequestMethodDELETE) { - request.requestTask = [self dataTaskWithHTTPMethod:@"DELETE" requestSerializer:requestSerializer URLString:url parameters:param]; - } else if (method == YTKRequestMethodPATCH) { - request.requestTask = [self dataTaskWithHTTPMethod:@"PATCH" requestSerializer:requestSerializer URLString:url parameters:param]; - } else { - YTKLog(@"Error, unsupport method type"); - return; - } + request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError]; } - if (!request.requestTask) { + if (requestSerializationError) { + [self requestDidFailWithRequest:request error:requestSerializationError]; return; } + // Set request task priority // !!Available on iOS 8 + if ([request.requestTask respondsToSelector:@selector(priority)]) { @@ -318,9 +255,12 @@ - (void)cancelAllRequests { } } -- (BOOL)checkResult:(YTKBaseRequest *)request { +- (BOOL)validateResult:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error { BOOL result = [request statusCodeValidator]; if (!result) { + if (error) { + *error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidStatusCode userInfo:@{NSLocalizedDescriptionKey:@"Invalid status code"}]; + } return result; } id validator = [request jsonValidator]; @@ -328,9 +268,15 @@ - (BOOL)checkResult:(YTKBaseRequest *)request { id json = [request responseJSONObject]; if (json) { result = [YTKNetworkUtils validateJSON:json withValidator:validator]; + if (!result) { + if (error) { + *error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidJSONFormat userInfo:@{NSLocalizedDescriptionKey:@"Invalid JSON format"}]; + } + return result; + } } } - return result; + return YES; } - (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error { @@ -338,81 +284,104 @@ - (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)response Lock(); YTKBaseRequest *request = _requestsRecord[key]; Unlock(); - if (request) { - YTKLog(@"Finished Request: %@", NSStringFromClass([request class])); - - NSError * __autoreleasing serializationError = nil; - BOOL succeed = NO; - - request.responseObject = responseObject; - if ([request.responseObject isKindOfClass:[NSData class]]) { - request.responseData = responseObject; - request.responseString = [[NSString alloc] initWithData:responseObject encoding:[YTKNetworkUtils stringEncodingWithRequest:request]]; - - switch (request.responseSerializerType) { - case YTKResponseSerializerTypeHTTP: - // Default serializer. Do nothing. - break; - case YTKResponseSerializerTypeJSON: - request.responseObject = [self.jsonResponseSerializer responseObjectForResponse:task.response data:request.responseData error:&serializationError]; - request.responseJSONObject = request.responseObject; - break; - case YTKResponseSerializerTypeXMLParser: - request.responseObject = [self.xmlParserResponseSerialzier responseObjectForResponse:task.response data:request.responseData error:&serializationError]; - break; - } - succeed = (error == nil) && (serializationError == nil) && [self checkResult:request]; - } else { - // Network error, or Download Task - succeed = (error == nil) && [self checkResult:request]; + + if (!request) { + return; + } + + YTKLog(@"Finished Request: %@", NSStringFromClass([request class])); + + NSError * __autoreleasing serializationError = nil; + NSError * __autoreleasing validationError = nil; + + NSError *requestError = nil; + BOOL succeed = NO; + + request.responseObject = responseObject; + if ([request.responseObject isKindOfClass:[NSData class]]) { + request.responseData = responseObject; + request.responseString = [[NSString alloc] initWithData:responseObject encoding:[YTKNetworkUtils stringEncodingWithRequest:request]]; + + switch (request.responseSerializerType) { + case YTKResponseSerializerTypeHTTP: + // Default serializer. Do nothing. + break; + case YTKResponseSerializerTypeJSON: + request.responseObject = [self.jsonResponseSerializer responseObjectForResponse:task.response data:request.responseData error:&serializationError]; + request.responseJSONObject = request.responseObject; + break; + case YTKResponseSerializerTypeXMLParser: + request.responseObject = [self.xmlParserResponseSerialzier responseObjectForResponse:task.response data:request.responseData error:&serializationError]; + break; } - if (succeed) { - @autoreleasepool { - [request requestCompletePreprocessor]; - } - dispatch_async(dispatch_get_main_queue(), ^{ - [request toggleAccessoriesWillStopCallBack]; - [request requestCompleteFilter]; + } + if (error) { + succeed = NO; + requestError = error; + } else if (serializationError) { + succeed = NO; + requestError = serializationError; + } else { + succeed = [self validateResult:request error:&validationError]; + requestError = validationError; + } - if (request.delegate != nil) { - [request.delegate requestFinished:request]; - } - if (request.successCompletionBlock) { - request.successCompletionBlock(request); - } - [request toggleAccessoriesDidStopCallBack]; - }); - } else { - request.error = serializationError ?: error; - YTKLog(@"Request %@ failed, status code = %ld, error = %@", - NSStringFromClass([request class]), (long)request.responseStatusCode, error.localizedDescription); - NSData *incompleteDownloadData = error.userInfo[NSURLSessionDownloadTaskResumeData]; - if (incompleteDownloadData) { - [incompleteDownloadData writeToURL:[self incompleteDownloadTempPathForDownloadRequest:request] atomically:YES]; - } + if (succeed) { + [self requestDidSucceedWithRequest:request]; + } else { + [self requestDidFailWithRequest:request error:requestError]; + } - @autoreleasepool { - [request requestFailedPreprocessor]; - } - dispatch_async(dispatch_get_main_queue(), ^{ - [request toggleAccessoriesWillStopCallBack]; - [request requestFailedFilter]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self removeRequestFromRecord:request]; + [request clearCompletionBlock]; + }); +} - if (request.delegate != nil) { - [request.delegate requestFailed:request]; - } - if (request.failureCompletionBlock) { - request.failureCompletionBlock(request); - } - [request toggleAccessoriesDidStopCallBack]; - }); +- (void)requestDidSucceedWithRequest:(YTKBaseRequest *)request { + @autoreleasepool { + [request requestCompletePreprocessor]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + [request toggleAccessoriesWillStopCallBack]; + [request requestCompleteFilter]; + + if (request.delegate != nil) { + [request.delegate requestFinished:request]; + } + if (request.successCompletionBlock) { + request.successCompletionBlock(request); } - dispatch_async(dispatch_get_main_queue(), ^{ - [self removeRequestFromRecord:request]; - [request clearCompletionBlock]; - }); + [request toggleAccessoriesDidStopCallBack]; + }); +} + +- (void)requestDidFailWithRequest:(YTKBaseRequest *)request error:(NSError *)error { + request.error = error; + YTKLog(@"Request %@ failed, status code = %ld, error = %@", + NSStringFromClass([request class]), (long)request.responseStatusCode, error.localizedDescription); + + NSData *incompleteDownloadData = error.userInfo[NSURLSessionDownloadTaskResumeData]; + if (incompleteDownloadData) { + [incompleteDownloadData writeToURL:[self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath] atomically:YES]; } + + @autoreleasepool { + [request requestFailedPreprocessor]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + [request toggleAccessoriesWillStopCallBack]; + [request requestFailedFilter]; + + if (request.delegate != nil) { + [request.delegate requestFailed:request]; + } + if (request.failureCompletionBlock) { + request.failureCompletionBlock(request); + } + [request toggleAccessoriesDidStopCallBack]; + }); } - (NSString *)requestHashKey:(NSURLSessionTask *)task { @@ -438,26 +407,96 @@ - (void)removeRequestFromRecord:(YTKBaseRequest *)request { Unlock(); } +#pragma mark - + +- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method + requestSerializer:(AFHTTPRequestSerializer *)requestSerializer + URLString:(NSString *)URLString + parameters:(id)parameters + error:(NSError * _Nullable __autoreleasing *)error { + return [self dataTaskWithHTTPMethod:method requestSerializer:requestSerializer URLString:URLString parameters:parameters constructingBodyWithBlock:nil error:error]; +} + - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method requestSerializer:(AFHTTPRequestSerializer *)requestSerializer URLString:(NSString *)URLString - parameters:(id)parameters { - NSError *serializationError = nil; - NSMutableURLRequest *request = [requestSerializer requestWithMethod:method URLString:URLString parameters:parameters error:&serializationError]; - if (serializationError) { - YTKLog(@"Request serialization failed, error = %@", serializationError.localizedDescription); - return nil; + parameters:(id)parameters + constructingBodyWithBlock:(nullable void (^)(id formData))block + error:(NSError * _Nullable __autoreleasing *)error { + NSMutableURLRequest *request = nil; + + if (block) { + request = [requestSerializer multipartFormRequestWithMethod:method URLString:URLString parameters:parameters constructingBodyWithBlock:block error:error]; + } else { + request = [requestSerializer requestWithMethod:method URLString:URLString parameters:parameters error:error]; } __block NSURLSessionDataTask *dataTask = nil; dataTask = [_manager dataTaskWithRequest:request - completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { - [self handleRequestResult:dataTask responseObject:responseObject error:error]; + completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *_error) { + [self handleRequestResult:dataTask responseObject:responseObject error:_error]; }]; return dataTask; } +- (NSURLSessionDownloadTask *)downloadTaskWithDownloadPath:(NSString *)downloadPath + requestSerializer:(AFHTTPRequestSerializer *)requestSerializer + URLString:(NSString *)URLString + parameters:(id)parameters + progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock + error:(NSError * _Nullable __autoreleasing *)error { + // add parameters to URL; + NSMutableURLRequest *urlRequest = [requestSerializer requestWithMethod:@"GET" URLString:URLString parameters:parameters error:error]; + + NSString *downloadTargetPath; + BOOL isDirectory; + if(![[NSFileManager defaultManager] fileExistsAtPath:downloadPath isDirectory:&isDirectory]) { + isDirectory = NO; + } + // If targetPath is a directory, use the file name we got from the urlRequest. + // Make sure downloadTargetPath is always a file, not directory. + if (isDirectory) { + NSString *fileName = [urlRequest.URL lastPathComponent]; + downloadTargetPath = [NSString pathWithComponents:@[downloadPath, fileName]]; + } else { + downloadTargetPath = downloadPath; + } + + BOOL resumeDataFileExists = [[NSFileManager defaultManager] fileExistsAtPath:[self incompleteDownloadTempPathForDownloadPath:downloadPath].path]; + NSData *data = [NSData dataWithContentsOfURL:[self incompleteDownloadTempPathForDownloadPath:downloadPath]]; + BOOL resumeDataIsValid = [YTKNetworkUtils validateResumeData:data]; + + BOOL canBeResumed = resumeDataFileExists && resumeDataIsValid; + BOOL resumeSucceeded = NO; + __block NSURLSessionDownloadTask *downloadTask = nil; + // Try to resume with resumeData. + // Even though we try to validate the resumeData, this may still fail and raise excecption. + if (canBeResumed) { + @try { + downloadTask = [_manager downloadTaskWithResumeData:data progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) { + return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO]; + } completionHandler: + ^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) { + [self handleRequestResult:downloadTask responseObject:filePath error:error]; + }]; + resumeSucceeded = YES; + } @catch (NSException *exception) { + YTKLog(@"Resume download failed, reason = %@", exception.reason); + resumeSucceeded = NO; + } + } + if (!resumeSucceeded) { + downloadTask = [_manager downloadTaskWithRequest:urlRequest progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) { + return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO]; + } completionHandler: + ^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) { + [self handleRequestResult:downloadTask responseObject:filePath error:error]; + }]; + } + return downloadTask; +} + #pragma mark - Resumable Download - (NSString *)incompleteDownloadTempCacheFolder { @@ -477,9 +516,9 @@ - (NSString *)incompleteDownloadTempCacheFolder { return cacheFolder; } -- (NSURL *)incompleteDownloadTempPathForDownloadRequest:(YTKBaseRequest *)request { +- (NSURL *)incompleteDownloadTempPathForDownloadPath:(NSString *)downloadPath { NSString *tempPath = nil; - NSString *md5URLString = [YTKNetworkUtils md5StringFromString:request.resumableDownloadPath]; + NSString *md5URLString = [YTKNetworkUtils md5StringFromString:downloadPath]; tempPath = [[self incompleteDownloadTempCacheFolder] stringByAppendingPathComponent:md5URLString]; return [NSURL fileURLWithPath:tempPath]; } diff --git a/YTKNetworkTests/YTKNetworkRequestTests.m b/YTKNetworkTests/YTKNetworkRequestTests.m index 09c2fc8..a3315fb 100644 --- a/YTKNetworkTests/YTKNetworkRequestTests.m +++ b/YTKNetworkTests/YTKNetworkRequestTests.m @@ -48,8 +48,11 @@ - (void)testBuildRequestURL { [self _testBuildRequestURLWithBaseURL:@"https://www.example.com" detailURL:@"get" resultURL:@"https://www.example.com/get"]; [self _testBuildRequestURLWithBaseURL:@"http://www.example.com" detailURL:@"get/val" resultURL:@"http://www.example.com/get/val"]; [self _testBuildRequestURLWithBaseURL:@"http://www.example.com" detailURL:@"get/val/" resultURL:@"http://www.example.com/get/val/"]; + [self _testBuildRequestURLWithBaseURL:@"https://www.example.com" detailURL:@"httpEndpoint" resultURL:@"https://www.example.com/httpEndpoint"]; + [self _testBuildRequestURLWithBaseURL:@"" detailURL:@"http://www.example.com" resultURL:@"http://www.example.com"]; [self _testBuildRequestURLWithBaseURL:@"" detailURL:@"https://www.example.com" resultURL:@"https://www.example.com"]; + [self _testBuildRequestURLWithBaseURL:@"http://www.something.com" detailURL:@"https://www.example.com" resultURL:@"https://www.example.com"]; } - (void)testBasicHTTPRequest { @@ -103,7 +106,11 @@ - (void)testJSONValidator { [self expectSuccess:validateSuccess]; YTKJSONValidatorRequest *validateFailure = [[YTKJSONValidatorRequest alloc] initWithJSONValidator:@{@"headers": [NSDictionary class], @"args": [NSString class]} requestUrl:@"get?key1=value&key2=123456"]; - [self expectFailure:validateFailure]; + [self expectFailure:validateFailure withAssertion:^(YTKBaseRequest *request) { + NSError *error = request.error; + XCTAssertTrue([error.domain isEqualToString:YTKRequestValidationErrorDomain]); + XCTAssertTrue(error.code == YTKRequestValidationErrorInvalidJSONFormat); + }]; } - (void)testXMLRequest { @@ -112,6 +119,9 @@ - (void)testXMLRequest { XCTAssertNotNil(request); XCTAssertTrue([request.responseObject isMemberOfClass:[NSXMLParser class]]); }]; + + YTKXMLRequest *req2 = [[YTKXMLRequest alloc] initWithRequestUrl:@"get"]; + [self expectFailure:req2]; } - (void)testStatusCodeValidator { @@ -119,7 +129,11 @@ - (void)testStatusCodeValidator { [self expectSuccess:validateSuccess]; YTKStatusCodeValidatorRequest *validateFailure = [[YTKStatusCodeValidatorRequest alloc] initWithRequestUrl:@"status/200"]; - [self expectFailure:validateFailure]; + [self expectFailure:validateFailure withAssertion:^(YTKBaseRequest *request) { + NSError *error = request.error; + XCTAssertTrue([error.domain isEqualToString:YTKRequestValidationErrorDomain]); + XCTAssertTrue(error.code == YTKRequestValidationErrorInvalidStatusCode); + }]; } - (void)testBatchRequest {