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 #pragma mark - Networking
- (NSURLRequest *)requestWithXML:(NSString*)xml attachment:(BITHockeyAttachment *)attachment { - (NSData *)postBodyWithXML:(NSString *)xml attachment:(BITHockeyAttachment *)attachment boundary:(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 *boundary = @"----FOO";
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
[request setValue:contentType forHTTPHeaderField:@"Content-type"];
NSMutableData *postBody = [NSMutableData data]; NSMutableData *postBody = [NSMutableData data];
// [postBody appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[BITHockeyAppClient dataWithPostValue:BITHOCKEY_NAME [postBody appendData:[BITHockeyAppClient dataWithPostValue:BITHOCKEY_NAME
forKey:@"sdk" forKey:@"sdk"
boundary:boundary]]; 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]]; [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; 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 * 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 * @param xml The XML data that needs to be send to the server
*/ */
- (void)sendCrashReportWithFilename:(NSString *)filename xml:(NSString*)xml attachment:(BITHockeyAttachment *)attachment { - (void)sendCrashReportWithFilename:(NSString *)filename xml:(NSString*)xml attachment:(BITHockeyAttachment *)attachment {
id nsurlsessionClass = NSClassFromString(@"NSURLSessionUploadTask");
NSURLRequest* request = [self requestWithXML:xml attachment:attachment]; if (nsurlsessionClass && !bit_isRunningInAppExtension()) {
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
__weak typeof (self) weakSelf = self; NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
BITHTTPOperation *operation = [self.hockeyAppClient
operationWithURLRequest:request NSURLRequest *request = [self requestWithBoundary:kBITHockeyAppClientBoundary];
completion:^(BITHTTPOperation *operation, NSData* responseData, NSError *error) { NSData *data = [self postBodyWithXML:xml attachment:attachment boundary:kBITHockeyAppClientBoundary];
typeof (self) strongSelf = weakSelf;
_sendingInProgress = NO;
NSInteger statusCode = [operation.response statusCode];
if (nil == error) { __weak typeof (self) weakSelf = self;
if (nil == responseData || [responseData length] == 0) { NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request
error = [NSError errorWithDomain:kBITCrashErrorDomain fromData:data
code:BITCrashAPIReceivedEmptyResponse completionHandler:^(NSData *responseData, NSURLResponse *response, NSError *error) {
userInfo:@{ typeof (self) strongSelf = weakSelf;
NSLocalizedDescriptionKey: @"Sending failed with an empty response!"
} NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) response;
]; NSInteger statusCode = [httpResponse statusCode];
} else if (statusCode >= 200 && statusCode < 400) { [strongSelf processUploadResultWithFilename:filename responseData:responseData statusCode:statusCode error:error];
[strongSelf cleanCrashReportWithFilename:filename]; }];
// HockeyApp uses PList XML format // 5
NSMutableDictionary *response = [NSPropertyListSerialization propertyListWithData:responseData [uploadTask resume];
options:NSPropertyListMutableContainersAndLeaves } else {
format:nil NSMutableURLRequest *request = [self requestWithBoundary:kBITHockeyAppClientBoundary];
error:&error];
BITHockeyLog(@"INFO: Received API response: %@", response); NSData *postBody = [self postBodyWithXML:xml attachment:attachment boundary:kBITHockeyAppClientBoundary];
[request setHTTPBody:postBody];
if (strongSelf.delegate != nil &&
[strongSelf.delegate respondsToSelector:@selector(crashManagerDidFinishSendingCrashReport:)]) { __weak typeof (self) weakSelf = self;
[strongSelf.delegate crashManagerDidFinishSendingCrashReport:self]; BITHTTPOperation *operation = [self.hockeyAppClient
} operationWithURLRequest:request
completion:^(BITHTTPOperation *operation, NSData* responseData, NSError *error) {
// only if sending the crash report went successfully, continue with the next one (if there are more) typeof (self) strongSelf = weakSelf;
[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]); 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:)]) { if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashManagerWillSendCrashReport:)]) {
[self.delegate crashManagerWillSendCrashReport:self]; [self.delegate crashManagerWillSendCrashReport:self];
} }
BITHockeyLog(@"INFO: Sending crash reports started."); BITHockeyLog(@"INFO: Sending crash reports started.");
[self.hockeyAppClient enqeueHTTPOperation:operation];
} }
- (NSTimeInterval)timeintervalCrashInLastSessionOccured { - (NSTimeInterval)timeintervalCrashInLastSessionOccured {

View File

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

View File

@ -27,6 +27,8 @@
*/ */
#import "BITHockeyAppClient.h" #import "BITHockeyAppClient.h"
NSString * const kBITHockeyAppClientBoundary = @"----FOO";
@implementation BITHockeyAppClient @implementation BITHockeyAppClient
- (void)dealloc { - (void)dealloc {
[self cancelOperationsWithPath:nil method:nil]; [self cancelOperationsWithPath:nil method:nil];
@ -66,16 +68,15 @@
} else { } else {
//TODO: this is crap. Boundary must be the same as the one in appendData //TODO: this is crap. Boundary must be the same as the one in appendData
//unify this! //unify this!
NSString *boundary = @"----FOO"; NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", kBITHockeyAppClientBoundary];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
[request setValue:contentType forHTTPHeaderField:@"Content-type"]; [request setValue:contentType forHTTPHeaderField:@"Content-type"];
NSMutableData *postBody = [NSMutableData data]; NSMutableData *postBody = [NSMutableData data];
[params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { [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]; [request setHTTPBody:postBody];
} }