Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asynchronously calculate User-Agent #623

Merged
merged 1 commit into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
bhamiltoncx marked this conversation as resolved.
Show resolved Hide resolved
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
Loading