From 1335417f9feefd20e5b0e4b1e18e9010edb87043 Mon Sep 17 00:00:00 2001 From: "blundell@chromium.org" Date: Mon, 1 Sep 2014 11:02:57 +0000 Subject: Adding possibility for client to upload the file This CL adds three features that will allow the client to upload the report file. Three main modifications are made : - Allow upload url to have a file:// scheme, and write the HTTP request to file in that case - Split the request in two parts in case of a file:// scheme, the request time and the response time. A new API [handleNetworkResponse] is added. - Give the opportunity to the client to get the configuration NSDictionary to be able to recreate the breakpad context at response time. Patch by Olivier Robin Review URL: https://breakpad.appspot.com/2764002/ git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1368 4c0a9323-5329-0410-9bdc-e9ce6186880e --- src/client/ios/Breakpad.h | 22 ++++++++ src/client/ios/Breakpad.mm | 103 ++++++++++++++++++++++++++++++----- src/client/ios/BreakpadController.h | 14 +++++ src/client/ios/BreakpadController.mm | 31 +++++++++++ src/client/mac/sender/uploader.h | 8 +++ src/client/mac/sender/uploader.mm | 84 +++++++++++++++++----------- src/common/mac/HTTPMultipartUpload.m | 16 ++++-- 7 files changed, 224 insertions(+), 54 deletions(-) diff --git a/src/client/ios/Breakpad.h b/src/client/ios/Breakpad.h index 9d212f70..c099ad07 100644 --- a/src/client/ios/Breakpad.h +++ b/src/client/ios/Breakpad.h @@ -199,6 +199,9 @@ void BreakpadRemoveUploadParameter(BreakpadRef ref, NSString *key); // Returns the number of crash reports waiting to send to the server. int BreakpadGetCrashReportCount(BreakpadRef ref); +// Returns the next upload configuration. The report file is deleted. +NSDictionary *BreakpadGetNextReportConfiguration(BreakpadRef ref); + // Upload next report to the server. void BreakpadUploadNextReport(BreakpadRef ref); @@ -207,6 +210,25 @@ void BreakpadUploadNextReport(BreakpadRef ref); void BreakpadUploadNextReportWithParameters(BreakpadRef ref, NSDictionary *server_parameters); +// Upload a report to the server. +// |server_parameters| is additional server parameters to send. +// |configuration| is the configuration of the breakpad report to send. +void BreakpadUploadReportWithParametersAndConfiguration( + BreakpadRef ref, + NSDictionary *server_parameters, + NSDictionary *configuration); + +// Handles the network response of a breakpad upload. This function is needed if +// the actual upload is done by the Breakpad client. +// |configuration| is the configuration of the upload. It must contain the same +// fields as the configuration passed to +// BreakpadUploadReportWithParametersAndConfiguration. +// |data| and |error| contain the network response. +void BreakpadHandleNetworkResponse(BreakpadRef ref, + NSDictionary *configuration, + NSData *data, + NSError *error); + // Upload a file to the server. |data| is the content of the file to sent. // |server_parameters| is additional server parameters to send. void BreakpadUploadData(BreakpadRef ref, NSData *data, NSString *name, diff --git a/src/client/ios/Breakpad.mm b/src/client/ios/Breakpad.mm index 5c4043c4..ca856f8f 100644 --- a/src/client/ios/Breakpad.mm +++ b/src/client/ios/Breakpad.mm @@ -152,9 +152,15 @@ class Breakpad { void RemoveKeyValue(NSString *key); NSArray *CrashReportsToUpload(); NSString *NextCrashReportToUpload(); + NSDictionary *NextCrashReportConfiguration(); void UploadNextReport(NSDictionary *server_parameters); + void UploadReportWithConfiguration(NSDictionary *configuration, + NSDictionary *server_parameters); void UploadData(NSData *data, NSString *name, NSDictionary *server_parameters); + void HandleNetworkResponse(NSDictionary *configuration, + NSData *data, + NSError *error); NSDictionary *GenerateReport(NSDictionary *server_parameters); private: @@ -448,19 +454,39 @@ NSString *Breakpad::NextCrashReportToUpload() { return [NSString stringWithFormat:@"%@/%@", directory, config]; } +//============================================================================= +NSDictionary *Breakpad::NextCrashReportConfiguration() { + return [Uploader readConfigurationDataFromFile:NextCrashReportToUpload()]; +} + +//============================================================================= +void Breakpad::HandleNetworkResponse(NSDictionary *configuration, + NSData *data, + NSError *error) { + Uploader *uploader = [[[Uploader alloc] + initWithConfig:configuration] autorelease]; + [uploader handleNetworkResponse:data withError:error]; +} + +//============================================================================= +void Breakpad::UploadReportWithConfiguration(NSDictionary *configuration, + NSDictionary *server_parameters) { + Uploader *uploader = [[[Uploader alloc] + initWithConfig:configuration] autorelease]; + if (!uploader) + return; + for (NSString *key in server_parameters) { + [uploader addServerParameter:[server_parameters objectForKey:key] + forKey:key]; + } + [uploader report]; +} + //============================================================================= void Breakpad::UploadNextReport(NSDictionary *server_parameters) { - NSString *configFile = NextCrashReportToUpload(); - if (configFile) { - Uploader *uploader = [[[Uploader alloc] - initWithConfigFile:[configFile UTF8String]] autorelease]; - if (uploader) { - for (NSString *key in server_parameters) { - [uploader addServerParameter:[server_parameters objectForKey:key] - forKey:key]; - } - [uploader report]; - } + NSDictionary *configuration = NextCrashReportConfiguration(); + if (configuration) { + return UploadReportWithConfiguration(configuration, server_parameters); } } @@ -794,18 +820,65 @@ void BreakpadUploadNextReport(BreakpadRef ref) { BreakpadUploadNextReportWithParameters(ref, nil); } +//============================================================================= +NSDictionary *BreakpadGetNextReportConfiguration(BreakpadRef ref) { + try { + Breakpad *breakpad = (Breakpad *)ref; + if (breakpad) + return breakpad->NextCrashReportConfiguration(); + } catch(...) { // don't let exceptions leave this C API + fprintf(stderr, "BreakpadGetNextReportConfiguration() : error\n"); + } + return nil; +} + +//============================================================================= +void BreakpadUploadReportWithParametersAndConfiguration( + BreakpadRef ref, + NSDictionary *server_parameters, + NSDictionary *configuration) { + try { + Breakpad *breakpad = (Breakpad *)ref; + if (!breakpad || !configuration) + return; + breakpad->UploadReportWithConfiguration(configuration, server_parameters); + } catch(...) { // don't let exceptions leave this C API + fprintf(stderr, + "BreakpadUploadReportWithParametersAndConfiguration() : error\n"); + } + +} + //============================================================================= void BreakpadUploadNextReportWithParameters(BreakpadRef ref, NSDictionary *server_parameters) { + try { + Breakpad *breakpad = (Breakpad *)ref; + if (!breakpad) + return; + NSDictionary *configuration = breakpad->NextCrashReportConfiguration(); + if (!configuration) + return; + return BreakpadUploadReportWithParametersAndConfiguration(ref, + server_parameters, + configuration); + } catch(...) { // don't let exceptions leave this C API + fprintf(stderr, "BreakpadUploadNextReportWithParameters() : error\n"); + } +} + +void BreakpadHandleNetworkResponse(BreakpadRef ref, + NSDictionary *configuration, + NSData *data, + NSError *error) { try { // Not called at exception time Breakpad *breakpad = (Breakpad *)ref; + if (breakpad && configuration) + breakpad->HandleNetworkResponse(configuration,data, error); - if (breakpad) { - breakpad->UploadNextReport(server_parameters); - } } catch(...) { // don't let exceptions leave this C API - fprintf(stderr, "BreakpadUploadNextReport() : error\n"); + fprintf(stderr, "BreakpadHandleNetworkResponse() : error\n"); } } diff --git a/src/client/ios/BreakpadController.h b/src/client/ios/BreakpadController.h index cf1fa774..13609cb8 100644 --- a/src/client/ios/BreakpadController.h +++ b/src/client/ios/BreakpadController.h @@ -122,6 +122,20 @@ // Get the number of crash reports waiting to upload. - (void)getCrashReportCount:(void(^)(int))callback; +// Get the next report to upload. +// - If upload is disabled, callback will be called with (nil, -1). +// - If a delay is to be waited before sending, callback will be called with +// (nil, n), with n (> 0) being the number of seconds to wait. +// - if no delay is needed, callback will be called with (0, configuration), +// configuration being next report to upload, or nil if none is pending. +- (void)getNextReportConfigurationOrSendDelay: + (void(^)(NSDictionary*, int))callback; + +// Sends synchronously the report specified by |configuration|. This method is +// NOT thread safe and must be called from the breakpad thread. +- (void)threadUnsafeSendReportWithConfiguration:(NSDictionary*)configuration + withBreakpadRef:(BreakpadRef)ref; + @end #endif // CLIENT_IOS_HANDLER_IOS_BREAKPAD_CONTROLLER_H_ diff --git a/src/client/ios/BreakpadController.mm b/src/client/ios/BreakpadController.mm index a85dd68e..dd71cff6 100644 --- a/src/client/ios/BreakpadController.mm +++ b/src/client/ios/BreakpadController.mm @@ -155,6 +155,18 @@ NSString* GetPlatform() { }); } +// This method must be called from the breakpad queue. +- (void)threadUnsafeSendReportWithConfiguration:(NSDictionary*)configuration + withBreakpadRef:(BreakpadRef)ref { + NSAssert(started_, @"The controller must be started before " + "threadUnsafeSendReportWithConfiguration is called"); + if (breakpadRef_) { + BreakpadUploadReportWithParametersAndConfiguration(breakpadRef_, + uploadTimeParameters_, + configuration); + } +} + - (void)setUploadingEnabled:(BOOL)enabled { NSAssert(started_, @"The controller must be started before setUploadingEnabled is called"); @@ -260,6 +272,25 @@ NSString* GetPlatform() { }); } +- (void)getNextReportConfigurationOrSendDelay: + (void(^)(NSDictionary*, int))callback { + NSAssert(started_, @"The controller must be started before " + "getNextReportConfigurationOrSendDelay is called"); + dispatch_async(queue_, ^{ + if (!breakpadRef_) { + callback(nil, -1); + return; + } + int delay = [self sendDelay]; + if (delay != 0) { + callback(nil, delay); + return; + } + [self reportWillBeSent]; + callback(BreakpadGetNextReportConfiguration(breakpadRef_), 0); + }); +} + #pragma mark - - (int)sendDelay { diff --git a/src/client/mac/sender/uploader.h b/src/client/mac/sender/uploader.h index 318165c9..5f6aa464 100644 --- a/src/client/mac/sender/uploader.h +++ b/src/client/mac/sender/uploader.h @@ -67,6 +67,10 @@ extern NSString *const kDefaultServerType; - (id)initWithConfig:(NSDictionary *)config; +// Reads the file |configFile| and returns the corresponding NSDictionary. +// |configFile| will be deleted after reading. ++ (NSDictionary *)readConfigurationDataFromFile:(NSString *)configFile; + - (NSMutableDictionary *)parameters; - (void)report; @@ -78,4 +82,8 @@ extern NSString *const kDefaultServerType; // will be uploaded to the crash server. - (void)addServerParameter:(id)value forKey:(NSString *)key; +// This method process the HTTP response and renames the minidump file with the +// new ID. +- (void)handleNetworkResponse:(NSData *)data withError:(NSError *)error; + @end diff --git a/src/client/mac/sender/uploader.mm b/src/client/mac/sender/uploader.mm index b6da8214..42a43bfc 100644 --- a/src/client/mac/sender/uploader.mm +++ b/src/client/mac/sender/uploader.mm @@ -204,6 +204,11 @@ NSDictionary *readConfigurationData(const char *configFile) { return self; } +//============================================================================= ++ (NSDictionary *)readConfigurationDataFromFile:(NSString *)configFile { + return readConfigurationData([configFile fileSystemRepresentation]); +} + //============================================================================= - (void)translateConfigurationData:(NSDictionary *)config { parameters_ = [[NSMutableDictionary alloc] init]; @@ -486,6 +491,46 @@ NSDictionary *readConfigurationData(const char *configFile) { [extraServerVars_ setObject:value forKey:key]; } +//============================================================================= +- (void)handleNetworkResponse:(NSData *)data withError:(NSError *)error { + NSString *result = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + const char *reportID = "ERR"; + if (error) { + fprintf(stderr, "Breakpad Uploader: Send Error: %s\n", + [[error description] UTF8String]); + } else { + NSCharacterSet *trimSet = + [NSCharacterSet whitespaceAndNewlineCharacterSet]; + reportID = [[result stringByTrimmingCharactersInSet:trimSet] UTF8String]; + [self logUploadWithID:reportID]; + } + + // rename the minidump file according to the id returned from the server + NSString *minidumpDir = + [parameters_ objectForKey:@kReporterMinidumpDirectoryKey]; + NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey]; + + NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp", + minidumpDir, minidumpID]; + NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp", + minidumpDir, reportID]; + + const char *src = [srcString fileSystemRepresentation]; + const char *dest = [destString fileSystemRepresentation]; + + if (rename(src, dest) == 0) { + GTMLoggerInfo(@"Breakpad Uploader: Renamed %s to %s after successful " \ + "upload",src, dest); + } + else { + // can't rename - don't worry - it's not important for users + GTMLoggerDebug(@"Breakpad Uploader: successful upload report ID = %s\n", + reportID ); + } + [result release]; +} + //============================================================================= - (void)report { NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]]; @@ -511,43 +556,16 @@ NSDictionary *readConfigurationData(const char *configFile) { // Send it NSError *error = nil; NSData *data = [upload send:&error]; - NSString *result = [[NSString alloc] initWithData:data - encoding:NSUTF8StringEncoding]; - const char *reportID = "ERR"; - if (error) { - fprintf(stderr, "Breakpad Uploader: Send Error: %s\n", - [[error description] UTF8String]); + if (![url isFileURL]) { + [self handleNetworkResponse:data withError:error]; } else { - NSCharacterSet *trimSet = - [NSCharacterSet whitespaceAndNewlineCharacterSet]; - reportID = [[result stringByTrimmingCharactersInSet:trimSet] UTF8String]; - [self logUploadWithID:reportID]; + if (error) { + fprintf(stderr, "Breakpad Uploader: Error writing request file: %s\n", + [[error description] UTF8String]); + } } - // rename the minidump file according to the id returned from the server - NSString *minidumpDir = - [parameters_ objectForKey:@kReporterMinidumpDirectoryKey]; - NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey]; - - NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp", - minidumpDir, minidumpID]; - NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp", - minidumpDir, reportID]; - - const char *src = [srcString fileSystemRepresentation]; - const char *dest = [destString fileSystemRepresentation]; - - if (rename(src, dest) == 0) { - GTMLoggerInfo(@"Breakpad Uploader: Renamed %s to %s after successful " \ - "upload",src, dest); - } - else { - // can't rename - don't worry - it's not important for users - GTMLoggerDebug(@"Breakpad Uploader: successful upload report ID = %s\n", - reportID ); - } - [result release]; } else { // Minidump is missing -- upload just the log file. if (logFileData_) { diff --git a/src/common/mac/HTTPMultipartUpload.m b/src/common/mac/HTTPMultipartUpload.m index 76f38f8a..bd88cffa 100644 --- a/src/common/mac/HTTPMultipartUpload.m +++ b/src/common/mac/HTTPMultipartUpload.m @@ -143,7 +143,7 @@ //============================================================================= - (NSData *)send:(NSError **)error { - NSMutableURLRequest *req = + NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url_ cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0 ]; @@ -190,12 +190,16 @@ [response_ release]; response_ = nil; - - NSData *data = [NSURLConnection sendSynchronousRequest:req - returningResponse:&response_ - error:error]; - [response_ retain]; + NSData *data; + if ([[req URL] isFileURL]) { + [[req HTTPBody] writeToURL:[req URL] options:0 error:error]; + } else { + data = [NSURLConnection sendSynchronousRequest:req + returningResponse:&response_ + error:error]; + [response_ retain]; + } [req release]; return data; -- cgit v1.2.1