Send crash reports via NSURLSession when available

This commit is contained in:
Andreas Linde 2015-05-22 18:05:47 +02:00
parent 5305474388
commit 3094c0524c
3 changed files with 124 additions and 87 deletions

View File

@ -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 {
id nsurlsessionClass = NSClassFromString(@"NSURLSessionUploadTask");
if (nsurlsessionClass && !bit_isRunningInAppExtension()) {
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSURLRequest* request = [self requestWithXML:xml attachment:attachment];
NSURLRequest *request = [self requestWithBoundary:kBITHockeyAppClientBoundary];
NSData *data = [self postBodyWithXML:xml attachment:attachment boundary:kBITHockeyAppClientBoundary];
__weak typeof (self) weakSelf = self;
BITHTTPOperation *operation = [self.hockeyAppClient
operationWithURLRequest:request
completion:^(BITHTTPOperation *operation, NSData* responseData, NSError *error) {
typeof (self) strongSelf = weakSelf;
__weak typeof (self) weakSelf = self;
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request
fromData:data
completionHandler:^(NSData *responseData, NSURLResponse *response, NSError *error) {
typeof (self) strongSelf = weakSelf;
_sendingInProgress = NO;
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) response;
NSInteger statusCode = [httpResponse statusCode];
[strongSelf processUploadResultWithFilename:filename responseData:responseData statusCode:statusCode error:error];
}];
NSInteger statusCode = [operation.response statusCode];
// 5
[uploadTask resume];
} else {
NSMutableURLRequest *request = [self requestWithBoundary: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];
NSData *postBody = [self postBodyWithXML:xml attachment:attachment boundary:kBITHockeyAppClientBoundary];
[request setHTTPBody:postBody];
// HockeyApp uses PList XML format
NSMutableDictionary *response = [NSPropertyListSerialization propertyListWithData:responseData
options:NSPropertyListMutableContainersAndLeaves
format:nil
error:&error];
BITHockeyLog(@"INFO: Received API response: %@", response);
__weak typeof (self) weakSelf = self;
BITHTTPOperation *operation = [self.hockeyAppClient
operationWithURLRequest:request
completion:^(BITHTTPOperation *operation, NSData* responseData, NSError *error) {
typeof (self) strongSelf = weakSelf;
if (strongSelf.delegate != nil &&
[strongSelf.delegate respondsToSelector:@selector(crashManagerDidFinishSendingCrashReport:)]) {
[strongSelf.delegate crashManagerDidFinishSendingCrashReport:self];
}
NSInteger statusCode = [operation.response statusCode];
[strongSelf processUploadResultWithFilename:filename responseData:responseData statusCode:statusCode error:error];
}];
// 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];
}
BITHockeyLog(@"ERROR: %@", [error localizedDescription]);
}
}];
[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 {

View File

@ -30,6 +30,8 @@
#import "BITHTTPOperation.h" //needed for typedef
extern NSString * const kBITHockeyAppClientBoundary;
/**
* Generic Hockey API client
*/

View File

@ -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];
}