From 3094c0524c92c21cfba9e369ea8d5612dafdfa2d Mon Sep 17 00:00:00 2001 From: Andreas Linde Date: Fri, 22 May 2015 18:05:47 +0200 Subject: [PATCH] Send crash reports via NSURLSession when available --- Classes/BITCrashManager.m | 200 ++++++++++++++++++++--------------- Classes/BITHockeyAppClient.h | 2 + Classes/BITHockeyAppClient.m | 9 +- 3 files changed, 124 insertions(+), 87 deletions(-) diff --git a/Classes/BITCrashManager.m b/Classes/BITCrashManager.m index c0b338cdc3..22df28e397 100644 --- a/Classes/BITCrashManager.m +++ b/Classes/BITCrashManager.m @@ -1464,23 +1464,10 @@ static void uncaught_cxx_exception_handler(const BITCrashUncaughtCXXExceptionInf #pragma mark - Networking -- (NSURLRequest *)requestWithXML:(NSString*)xml attachment:(BITHockeyAttachment *)attachment { - NSString *postCrashPath = [NSString stringWithFormat:@"api/2/apps/%@/crashes", self.encodedAppIdentifier]; - - NSMutableURLRequest *request = [self.hockeyAppClient requestWithMethod:@"POST" - path:postCrashPath - parameters:nil]; - - [request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData]; - [request setValue:@"HockeySDK/iOS" forHTTPHeaderField:@"User-Agent"]; - [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; - - NSString *boundary = @"----FOO"; - NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; - [request setValue:contentType forHTTPHeaderField:@"Content-type"]; - +- (NSData *)postBodyWithXML:(NSString *)xml attachment:(BITHockeyAttachment *)attachment boundary:(NSString *)boundary { NSMutableData *postBody = [NSMutableData data]; +// [postBody appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[BITHockeyAppClient dataWithPostValue:BITHOCKEY_NAME forKey:@"sdk" boundary:boundary]]; @@ -1513,11 +1500,88 @@ static void uncaught_cxx_exception_handler(const BITCrashUncaughtCXXExceptionInf [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; - [request setHTTPBody:postBody]; + return postBody; +} + +- (NSMutableURLRequest *)requestWithBoundary:(NSString *)boundary { + NSString *postCrashPath = [NSString stringWithFormat:@"api/2/apps/%@/crashes", self.encodedAppIdentifier]; + NSMutableURLRequest *request = [self.hockeyAppClient requestWithMethod:@"POST" + path:postCrashPath + parameters:nil]; + + [request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData]; + [request setValue:@"HockeySDK/iOS" forHTTPHeaderField:@"User-Agent"]; + [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; + + NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; + [request setValue:contentType forHTTPHeaderField:@"Content-type"]; + return request; } +// process upload response +- (void)processUploadResultWithFilename:(NSString *)filename responseData:(NSData *)responseData statusCode:(NSInteger)statusCode error:(NSError *)error { + __block NSError *theError = error; + + dispatch_async(dispatch_get_main_queue(), ^{ + _sendingInProgress = NO; + + if (nil == theError) { + if (nil == responseData || [responseData length] == 0) { + theError = [NSError errorWithDomain:kBITCrashErrorDomain + code:BITCrashAPIReceivedEmptyResponse + userInfo:@{ + NSLocalizedDescriptionKey: @"Sending failed with an empty response!" + } + ]; + } else if (statusCode >= 200 && statusCode < 400) { + [self cleanCrashReportWithFilename:filename]; + + // HockeyApp uses PList XML format + NSMutableDictionary *response = [NSPropertyListSerialization propertyListWithData:responseData + options:NSPropertyListMutableContainersAndLeaves + format:nil + error:&theError]; + BITHockeyLog(@"INFO: Received API response: %@", response); + + if (self.delegate != nil && + [self.delegate respondsToSelector:@selector(crashManagerDidFinishSendingCrashReport:)]) { + [self.delegate crashManagerDidFinishSendingCrashReport:self]; + } + + // only if sending the crash report went successfully, continue with the next one (if there are more) + [self sendNextCrashReport]; + } else if (statusCode == 400) { + [self cleanCrashReportWithFilename:filename]; + + theError = [NSError errorWithDomain:kBITCrashErrorDomain + code:BITCrashAPIAppVersionRejected + userInfo:@{ + NSLocalizedDescriptionKey: @"The server rejected receiving crash reports for this app version!" + } + ]; + } else { + theError = [NSError errorWithDomain:kBITCrashErrorDomain + code:BITCrashAPIErrorWithStatusCode + userInfo:@{ + NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Sending failed with status code: %li", (long)statusCode] + } + ]; + } + } + + if (theError) { + if (self.delegate != nil && + [self.delegate respondsToSelector:@selector(crashManager:didFailWithError:)]) { + [self.delegate crashManager:self didFailWithError:theError]; + } + + BITHockeyLog(@"ERROR: %@", [theError localizedDescription]); + } + }); +} + /** * Send the XML data to the server * @@ -1526,81 +1590,51 @@ static void uncaught_cxx_exception_handler(const BITCrashUncaughtCXXExceptionInf * @param xml The XML data that needs to be send to the server */ - (void)sendCrashReportWithFilename:(NSString *)filename xml:(NSString*)xml attachment:(BITHockeyAttachment *)attachment { - - NSURLRequest* request = [self requestWithXML:xml attachment:attachment]; - - __weak typeof (self) weakSelf = self; - BITHTTPOperation *operation = [self.hockeyAppClient - operationWithURLRequest:request - completion:^(BITHTTPOperation *operation, NSData* responseData, NSError *error) { - typeof (self) strongSelf = weakSelf; - - _sendingInProgress = NO; - - NSInteger statusCode = [operation.response statusCode]; + id nsurlsessionClass = NSClassFromString(@"NSURLSessionUploadTask"); + if (nsurlsessionClass && !bit_isRunningInAppExtension()) { + NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; + + NSURLRequest *request = [self requestWithBoundary:kBITHockeyAppClientBoundary]; + NSData *data = [self postBodyWithXML:xml attachment:attachment boundary:kBITHockeyAppClientBoundary]; - if (nil == error) { - if (nil == responseData || [responseData length] == 0) { - error = [NSError errorWithDomain:kBITCrashErrorDomain - code:BITCrashAPIReceivedEmptyResponse - userInfo:@{ - NSLocalizedDescriptionKey: @"Sending failed with an empty response!" - } - ]; - } else if (statusCode >= 200 && statusCode < 400) { - [strongSelf cleanCrashReportWithFilename:filename]; - - // HockeyApp uses PList XML format - NSMutableDictionary *response = [NSPropertyListSerialization propertyListWithData:responseData - options:NSPropertyListMutableContainersAndLeaves - format:nil - error:&error]; - BITHockeyLog(@"INFO: Received API response: %@", response); - - if (strongSelf.delegate != nil && - [strongSelf.delegate respondsToSelector:@selector(crashManagerDidFinishSendingCrashReport:)]) { - [strongSelf.delegate crashManagerDidFinishSendingCrashReport:self]; - } - - // only if sending the crash report went successfully, continue with the next one (if there are more) - [strongSelf sendNextCrashReport]; - } else if (statusCode == 400) { - [strongSelf cleanCrashReportWithFilename:filename]; - - error = [NSError errorWithDomain:kBITCrashErrorDomain - code:BITCrashAPIAppVersionRejected - userInfo:@{ - NSLocalizedDescriptionKey: @"The server rejected receiving crash reports for this app version!" - } - ]; - } else { - error = [NSError errorWithDomain:kBITCrashErrorDomain - code:BITCrashAPIErrorWithStatusCode - userInfo:@{ - NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Sending failed with status code: %li", (long)statusCode] - } - ]; - } - } - - if (error) { - if (strongSelf.delegate != nil && - [strongSelf.delegate respondsToSelector:@selector(crashManager:didFailWithError:)]) { - [strongSelf.delegate crashManager:self didFailWithError:error]; - } + __weak typeof (self) weakSelf = self; + NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request + fromData:data + completionHandler:^(NSData *responseData, NSURLResponse *response, NSError *error) { + typeof (self) strongSelf = weakSelf; + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) response; + NSInteger statusCode = [httpResponse statusCode]; + [strongSelf processUploadResultWithFilename:filename responseData:responseData statusCode:statusCode error:error]; + }]; + + // 5 + [uploadTask resume]; + } else { + NSMutableURLRequest *request = [self requestWithBoundary:kBITHockeyAppClientBoundary]; + + NSData *postBody = [self postBodyWithXML:xml attachment:attachment boundary:kBITHockeyAppClientBoundary]; + [request setHTTPBody:postBody]; + + __weak typeof (self) weakSelf = self; + BITHTTPOperation *operation = [self.hockeyAppClient + operationWithURLRequest:request + completion:^(BITHTTPOperation *operation, NSData* responseData, NSError *error) { + typeof (self) strongSelf = weakSelf; - BITHockeyLog(@"ERROR: %@", [error localizedDescription]); - } - - }]; + NSInteger statusCode = [operation.response statusCode]; + [strongSelf processUploadResultWithFilename:filename responseData:responseData statusCode:statusCode error:error]; + }]; + + [self.hockeyAppClient enqeueHTTPOperation:operation]; + } if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashManagerWillSendCrashReport:)]) { [self.delegate crashManagerWillSendCrashReport:self]; } BITHockeyLog(@"INFO: Sending crash reports started."); - - [self.hockeyAppClient enqeueHTTPOperation:operation]; } - (NSTimeInterval)timeintervalCrashInLastSessionOccured { diff --git a/Classes/BITHockeyAppClient.h b/Classes/BITHockeyAppClient.h index b4abe93e0a..3092beccc0 100644 --- a/Classes/BITHockeyAppClient.h +++ b/Classes/BITHockeyAppClient.h @@ -30,6 +30,8 @@ #import "BITHTTPOperation.h" //needed for typedef +extern NSString * const kBITHockeyAppClientBoundary; + /** * Generic Hockey API client */ diff --git a/Classes/BITHockeyAppClient.m b/Classes/BITHockeyAppClient.m index 39944284d8..5f293933a8 100644 --- a/Classes/BITHockeyAppClient.m +++ b/Classes/BITHockeyAppClient.m @@ -27,6 +27,8 @@ */ #import "BITHockeyAppClient.h" +NSString * const kBITHockeyAppClientBoundary = @"----FOO"; + @implementation BITHockeyAppClient - (void)dealloc { [self cancelOperationsWithPath:nil method:nil]; @@ -66,16 +68,15 @@ } else { //TODO: this is crap. Boundary must be the same as the one in appendData //unify this! - NSString *boundary = @"----FOO"; - NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; + NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", kBITHockeyAppClientBoundary]; [request setValue:contentType forHTTPHeaderField:@"Content-type"]; NSMutableData *postBody = [NSMutableData data]; [params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { - [postBody appendData:[[self class] dataWithPostValue:value forKey:key boundary:boundary]]; + [postBody appendData:[[self class] dataWithPostValue:value forKey:key boundary:kBITHockeyAppClientBoundary]]; }]; - [postBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + [postBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", kBITHockeyAppClientBoundary] dataUsingEncoding:NSUTF8StringEncoding]]; [request setHTTPBody:postBody]; }