Skip to content

Commit

Permalink
Asynchronously calculate User-Agent
Browse files Browse the repository at this point in the history
  • Loading branch information
bhamiltoncx committed Jan 22, 2024
1 parent bcb0439 commit c3ab108
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 52 deletions.
163 changes: 112 additions & 51 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,13 +455,25 @@ - (NSMutableURLRequest *)objectRequestForURL:(NSURL *)url
NSString *value = [headers objectForKey:key];
[request setValue:value forHTTPHeaderField:key];
}

return request;
}

#pragma mark -

- (NSMutableURLRequest *)requestForQuery:(GTLRQuery *)query {
dispatch_semaphore_t requestCompleteSemaphore = dispatch_semaphore_create(0);

__block NSMutableURLRequest *result;
[self requestForQuery:query
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 {
GTLR_DEBUG_ASSERT(query.bodyObject == nil,
@"requestForQuery: supports only GET methods, but was passed: %@", query);
GTLR_DEBUG_ASSERT(query.uploadParameters == nil,
Expand All @@ -446,10 +492,17 @@ - (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];
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 +519,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 +625,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 +634,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 +702,7 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
testBlock:testBlock
dataToPost:dataToPost
completionHandler:completionHandler];
return ticket;
return;
}

GTMSessionFetcherService *fetcherService = ticket.fetcherService;
Expand Down Expand Up @@ -814,24 +886,13 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
}

[self handleParsedObjectForFetcher:fetcher
executingQuery:executingQuery
ticket:ticket
error:error
parsedObject:nil
hasSentParsingStartNotification:NO
completionHandler:completionHandler];
executingQuery:executingQuery
ticket:ticket
error:error
parsedObject:nil
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
24 changes: 23 additions & 1 deletion Sources/Core/Public/GoogleAPIClientForREST/GTLRService.h
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,29 @@ typedef void (^GTLRServiceTestBlock)(GTLRServiceTicket *testTicket,
*
* @return A request suitable for use with @c GTMSessionFetcher or @c NSURLSession
*/
- (NSMutableURLRequest *)requestForQuery:(GTLRQuery *)query;
- (NSMutableURLRequest *)requestForQuery:(GTLRQuery *)query
__attribute__((deprecated("Blocks the caller to calculate the User-Agent. Use "
"-requestForQuery:completion: instead.")));

/**
* Creates a NSURLRequest from the query object and from properties on this service
* (additionalHTTPHeaders, additionalURLQueryParameters, APIKey) without executing
* it. This can be useful for using @c GTMSessionFetcher or @c NSURLSession to
* perform the fetch.
*
* For requests to non-public resources, the request will not yet be authorized;
* that can be done using the GTLR service's authorizer. Creating a @c GTMSessionFetcher
* from the GTLRService's @c fetcherService will take care of authorization as well.
*
* This works only for GET queries, and only for an individual query, not a batch query.
*
* @note @c Unlike executeQuery:, requestForQuery: does not release the query's callback blocks.
*
* @param query The query used to create the request.
* @param completion Completion invoked with the URL request suitable for use
* with with @c GTMSessionFetcher or @c NSURLSession
*/
- (void)requestForQuery:(GTLRQuery *)query completion:(void (^)(NSMutableURLRequest *))completion;

#pragma mark User Properties

Expand Down

0 comments on commit c3ab108

Please sign in to comment.