Skip to content

Commit

Permalink
Asynchronously calculate User-Agent
Browse files Browse the repository at this point in the history
  • Loading branch information
bhamiltoncx authored and thomasvl committed Jan 24, 2024
1 parent bcb0439 commit bfe8cd0
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 156 deletions.
22 changes: 12 additions & 10 deletions Examples/DriveSample/DriveSampleWindowController.m
Original file line number Diff line number Diff line change
Expand Up @@ -277,19 +277,21 @@ - (void)downloadFile:(GTLRDrive_File *)file
// Here's how to download with a GTMSessionFetcher. The fetcher will use the authorizer that's
// attached to the GTLR service's fetcherService.
//
// NSURLRequest *downloadRequest = [service requestForQuery:query];
// GTMSessionFetcher *fetcher = [service.fetcherService fetcherWithRequest:downloadRequest];
// [service requestForQuery:query
// completion:^(NSURLRequest *downloadRequest) {
// GTMSessionFetcher *fetcher = [service.fetcherService fetcherWithRequest:downloadRequest];
//
// [fetcher setCommentWithFormat:@"Downloading %@", file.name];
// fetcher.destinationFileURL = destinationURL;
// [fetcher setCommentWithFormat:@"Downloading %@", file.name];
// fetcher.destinationFileURL = destinationURL;
//
// [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
// if (error == nil) {
// NSLog(@"Download succeeded.");
// [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
// if (error == nil) {
// NSLog(@"Download succeeded.");
//
// // With a destinationFileURL property set, the fetcher's callback
// // data parameter here will be nil.
// }
// // With a destinationFileURL property set, the fetcher's callback
// // data parameter here will be nil.
// }
// }];
// }];

[service executeQuery:query
Expand Down
66 changes: 33 additions & 33 deletions Examples/StorageSample/StorageSampleWindowController.m
Original file line number Diff line number Diff line change
Expand Up @@ -193,39 +193,39 @@ - (IBAction)downloadFileClicked:(id)sender {
// Having the service execute this query would download the data to a GTLRDataObject.
// But for downloads that might be large, we'll use a fetcher, since that offers
// better control and monitoring of downloading.
NSURLRequest *request = [storageService requestForQuery:query];

// The Storage service's fetcherService will create a fetcher with an appropriate
// authorizer.
GTMSessionFetcher *fetcher = [storageService.fetcherService fetcherWithRequest:request];

// The fetcher can save data directly to a file.
fetcher.destinationFileURL = destinationURL;

// Fetcher logging can include comments.
[fetcher setCommentWithFormat:@"Downloading \"%@/%@\"",
storageObject.bucket, storageObject.name];

fetcher.downloadProgressBlock = ^(int64_t bytesWritten,
int64_t totalBytesWritten,
int64_t totalBytesExpectedToWrite) {
// The fetcher will call the download progress block periodically.
};

[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
// Callback
if (error == nil) {
// Successfully saved the file.
//
// Since a downloadPath property was specified, the data argument is
// nil, and the file data has been written to disk.
[self displayAlert:@"Downloaded"
format:@"%@", destinationURL.path];
} else {
[self displayAlert:@"Error Downloading File"
format:@"%@", error];
}
}];
[storageService
requestForQuery:query
completion:^(NSURLRequest *request) {
// The Storage service's fetcherService will create a fetcher with an appropriate
// authorizer.
GTMSessionFetcher *fetcher =
[storageService.fetcherService fetcherWithRequest:request];

// The fetcher can save data directly to a file.
fetcher.destinationFileURL = destinationURL;

// Fetcher logging can include comments.
[fetcher setCommentWithFormat:@"Downloading \"%@/%@\"", storageObject.bucket,
storageObject.name];

fetcher.downloadProgressBlock = ^(int64_t bytesWritten, int64_t totalBytesWritten,
int64_t totalBytesExpectedToWrite) {
// The fetcher will call the download progress block periodically.
};

[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
// Callback
if (error == nil) {
// Successfully saved the file.
//
// Since a downloadPath property was specified, the data argument is
// nil, and the file data has been written to disk.
[self displayAlert:@"Downloaded" format:@"%@", destinationURL.path];
} else {
[self displayAlert:@"Error Downloading File" format:@"%@", error];
}
}];
}];
} // result == NSFileHandlingPanelOKButton
}]; // beginSheetModalForWindow:
}
Expand Down
173 changes: 125 additions & 48 deletions Sources/Core/GTLRService.m
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ @implementation GTLRService {
NSString *_overrideUserAgent;
NSDictionary *_serviceProperties; // Properties retained for the convenience of the client app.
NSUInteger _uploadChunkSize; // Only applies to resumable chunked uploads.
dispatch_queue_t _requestCreationQueue;
}

@synthesize additionalHTTPHeaders = _additionalHTTPHeaders,
Expand Down Expand Up @@ -250,6 +251,9 @@ - (instancetype)init {
if (self) {
_parseQueue = dispatch_queue_create("com.google.GTLRServiceParse", DISPATCH_QUEUE_SERIAL);
_callbackQueue = dispatch_get_main_queue();
_requestCreationQueue =
dispatch_queue_create("com.google.GTLRServiceRequestCreation", DISPATCH_QUEUE_SERIAL);

_fetcherService = [[GTMSessionFetcherService alloc] init];

// Make the session fetcher use a background delegate queue instead of bouncing
Expand Down Expand Up @@ -319,10 +323,26 @@ - (void)setMainBundleIDRestrictionWithAPIKey:(NSString *)apiKey {
self.APIKeyRestrictionBundleID = [[NSBundle mainBundle] bundleIdentifier];
}

- (NSMutableURLRequest *)requestForURL:(NSURL *)url
ETag:(NSString *)etag
httpMethod:(NSString *)httpMethod
ticket:(GTLRServiceTicket *)ticket {
- (void)requestForURL:(NSURL *)url
ETag:(NSString *)etag
httpMethod:(NSString *)httpMethod
ticket:(GTLRServiceTicket *)ticket
completion:(void (^)(NSMutableURLRequest *))completion {
dispatch_async(_requestCreationQueue, ^{
NSMutableURLRequest *request = [self createRequestForURL:url
ETag:etag
httpMethod:httpMethod
ticket:ticket];
completion(request);
});
}

- (NSMutableURLRequest *)createRequestForURL:(NSURL *)url
ETag:(NSString *)etag
httpMethod:(NSString *)httpMethod
ticket:(GTLRServiceTicket *)ticket {
// This method may block, so make sure it's not on the caller's queue when executing a query.
dispatch_assert_queue_debug(_requestCreationQueue);

// subclasses may add headers to this
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
Expand Down Expand Up @@ -371,19 +391,20 @@ - (NSMutableURLRequest *)requestForURL:(NSURL *)url
return request;
}

// objectRequestForURL returns an NSMutableURLRequest for a GTLRObject
// objectRequestForURL asynchronously returns an NSMutableURLRequest for a GTLRObject
//
// the object is the object being sent to the server, or nil;
// the http method may be nil for get, or POST, PUT, DELETE

- (NSMutableURLRequest *)objectRequestForURL:(NSURL *)url
object:(GTLRObject *)object
contentType:(NSString *)contentType
contentLength:(NSString *)contentLength
ETag:(NSString *)etag
httpMethod:(NSString *)httpMethod
additionalHeaders:(NSDictionary *)additionalHeaders
ticket:(GTLRServiceTicket *)ticket {
- (void)objectRequestForURL:(NSURL *)url
object:(GTLRObject *)object
contentType:(NSString *)contentType
contentLength:(NSString *)contentLength
ETag:(NSString *)etag
httpMethod:(NSString *)httpMethod
additionalHeaders:(NSDictionary *)additionalHeaders
ticket:(GTLRServiceTicket *)ticket
completion:(void (^)(NSMutableURLRequest *))completion {
if (object) {
// if the object being sent has an etag, add it to the request header to
// avoid retrieving a duplicate or to avoid writing over an updated
Expand All @@ -396,10 +417,23 @@ - (NSMutableURLRequest *)objectRequestForURL:(NSURL *)url
}
}

NSMutableURLRequest *request = [self requestForURL:url
ETag:etag
httpMethod:httpMethod
ticket:ticket];
[self requestForURL:url
ETag:etag
httpMethod:httpMethod
ticket:ticket
completion:^(NSMutableURLRequest *request) {
[self handleRequestCompletion:request
contentType:contentType
contentLength:contentLength
additionalHeaders:additionalHeaders];
completion(request);
}];
}

- (void)handleRequestCompletion:(NSMutableURLRequest *)request
contentType:(NSString *)contentType
contentLength:(NSString *)contentLength
additionalHeaders:(NSDictionary<NSString *, NSString *> *)additionalHeaders {
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];

Expand All @@ -421,17 +455,43 @@ - (NSMutableURLRequest *)objectRequestForURL:(NSURL *)url
NSString *value = [headers objectForKey:key];
[request setValue:value forHTTPHeaderField:key];
}

return request;
}

#pragma mark -

- (NSMutableURLRequest *)requestForQuery:(GTLRQuery *)query {
GTLR_DEBUG_ASSERT(query.bodyObject == nil,
@"requestForQuery: supports only GET methods, but was passed: %@", query);
// This public API will block the calling thread waiting for a completion to be dispatched onto
// this queue, so it cannot be called on this queue.
//
// Thankfully, this queue is an internal implementation detail, so this just guards against
// someone in the future accidentally calling this method on the request creation queue.
dispatch_assert_queue_not_debug(_requestCreationQueue);

dispatch_semaphore_t requestCompleteSemaphore = dispatch_semaphore_create(0);

__block NSMutableURLRequest *result;
[self requestForQuery:query
completionQueue:_requestCreationQueue
completion:^(NSMutableURLRequest *request) {
result = request;
dispatch_semaphore_signal(requestCompleteSemaphore);
}];

dispatch_semaphore_wait(requestCompleteSemaphore, DISPATCH_TIME_FOREVER);
return result;
}

- (void)requestForQuery:(GTLRQuery *)query completion:(void (^)(NSMutableURLRequest *))completion {
[self requestForQuery:query completionQueue:self.callbackQueue completion:completion];
}

- (void)requestForQuery:(GTLRQuery *)query
completionQueue:(dispatch_queue_t)completionQueue
completion:(void (^)(NSMutableURLRequest *))completion {
GTLR_DEBUG_ASSERT(query.bodyObject == nil, @"%s supports only GET methods, but was passed: %@",
__func__, query);
GTLR_DEBUG_ASSERT(query.uploadParameters == nil,
@"requestForQuery: does not support uploads, but was passed: %@", query);
@"%s does not support uploads, but was passed: %@", __func__, query);

NSURL *url = [self URLFromQueryObject:query
usePartialPaths:NO
Expand All @@ -446,10 +506,19 @@ - (NSMutableURLRequest *)requestForQuery:(GTLRQuery *)query {
queryParameters:queryParameters];
}

NSMutableURLRequest *request = [self requestForURL:url
ETag:nil
httpMethod:query.httpMethod
ticket:nil];
[self requestForURL:url
ETag:nil
httpMethod:query.httpMethod
ticket:nil
completion:^(NSMutableURLRequest *request) {
[self handleRequestCompletion:request forQuery:query];
dispatch_async(completionQueue, ^{
completion(request);
});
}];
}

- (void)handleRequestCompletion:(NSMutableURLRequest *)request forQuery:(GTLRQuery *)query {
NSString *apiRestriction = self.APIKeyRestrictionBundleID;
if ([apiRestriction length] > 0) {
[request setValue:apiRestriction forHTTPHeaderField:kXIosBundleIdHeader];
Expand All @@ -466,8 +535,6 @@ - (NSMutableURLRequest *)requestForQuery:(GTLRQuery *)query {
NSString *value = [headers objectForKey:key];
[request setValue:value forHTTPHeaderField:key];
}

return request;
}

// common fetch starting method
Expand Down Expand Up @@ -574,14 +641,6 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
}
}

NSURLRequest *request = [self objectRequestForURL:targetURL
object:bodyObject
contentType:contentType
contentLength:contentLength
ETag:etag
httpMethod:httpMethod
additionalHeaders:additionalHeaders
ticket:ticket];
ticket.postedObject = bodyObject;
ticket.executingQuery = executingQuery;

Expand All @@ -591,10 +650,39 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
ticket.originalQuery = originalQuery;
}

[self objectRequestForURL:targetURL
object:bodyObject
contentType:contentType
contentLength:contentLength
ETag:etag
httpMethod:httpMethod
additionalHeaders:additionalHeaders
ticket:ticket
completion:^(NSMutableURLRequest *request) {
[self handleObjectRequestCompletionWithRequest:request
objectClass:objectClass
dataToPost:dataToPost
mayAuthorize:mayAuthorize
completionHandler:completionHandler
executingQuery:executingQuery
ticket:ticket];
}];

return ticket;
}

- (void)handleObjectRequestCompletionWithRequest:(NSMutableURLRequest *)request
objectClass:(Class)objectClass
dataToPost:(NSData *)dataToPost
mayAuthorize:(BOOL)mayAuthorize
completionHandler:(GTLRServiceCompletionHandler)completionHandler
executingQuery:(id<GTLRQueryProtocol>)executingQuery
ticket:(GTLRServiceTicket *)ticket {
// Some proxy servers (and some web servers) have issues with GET URLs being
// too long, trap that and move the query parameters into the body. The
// uploadParams and dataToPost should be nil for a GET, but playing it safe
// and confirming.
GTLRUploadParameters *uploadParams = executingQuery.uploadParameters;
NSString *requestHTTPMethod = request.HTTPMethod;
BOOL isDoingHTTPGet =
(requestHTTPMethod == nil
Expand Down Expand Up @@ -630,7 +718,7 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
testBlock:testBlock
dataToPost:dataToPost
completionHandler:completionHandler];
return ticket;
return;
}

GTMSessionFetcherService *fetcherService = ticket.fetcherService;
Expand Down Expand Up @@ -821,17 +909,6 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
hasSentParsingStartNotification:NO
completionHandler:completionHandler];
}]; // fetcher completion handler

// If something weird happens and the networking callbacks have been called
// already synchronously, we don't want to return the ticket since the caller
// will never know when to stop retaining it, so we'll make sure the
// success/failure callbacks have not yet been called by checking the
// ticket
if (ticket.hasCalledCallback) {
return nil;
}

return ticket;
}

- (GTMSessionUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request
Expand Down
Loading

0 comments on commit bfe8cd0

Please sign in to comment.