FIRIAMClearcutHttpRequestSender.m 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. /*
  2. * Copyright 2018 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <FirebaseCore/FIRLogger.h>
  17. #import "FIRCore+InAppMessaging.h"
  18. #import "FIRIAMClearcutHttpRequestSender.h"
  19. #import "FIRIAMClearcutLogStorage.h"
  20. #import "FIRIAMClientInfoFetcher.h"
  21. #import "FIRIAMTimeFetcher.h"
  22. @interface FIRIAMClearcutHttpRequestSender ()
  23. @property(readonly, copy, nonatomic) NSString *serverHostName;
  24. @property(readwrite, nonatomic) id<FIRIAMTimeFetcher> timeFetcher;
  25. @property(readonly, copy, nonatomic) NSString *osMajorVersion;
  26. @end
  27. @implementation FIRIAMClearcutHttpRequestSender
  28. - (instancetype)initWithClearcutHost:(NSString *)serverHost
  29. usingTimeFetcher:(id<FIRIAMTimeFetcher>)timeFetcher
  30. withOSMajorVersion:(NSString *)osMajorVersion {
  31. if (self = [super init]) {
  32. _serverHostName = [serverHost copy];
  33. _timeFetcher = timeFetcher;
  34. _osMajorVersion = [osMajorVersion copy];
  35. }
  36. return self;
  37. }
  38. - (void)updateRequestBodyWithClearcutEnvelopeFields:(NSMutableDictionary *)bodyDict {
  39. bodyDict[@"client_info"] = @{
  40. @"client_type" : @15, // 15 is the enum value for IOS_FIREBASE client
  41. @"ios_client_info" : @{@"os_major_version" : self.osMajorVersion ?: @""}
  42. };
  43. bodyDict[@"log_source"] = @"FIREBASE_INAPPMESSAGING";
  44. NSTimeInterval nowInMs = [self.timeFetcher currentTimestampInSeconds] * 1000;
  45. bodyDict[@"request_time_ms"] = @((long)nowInMs);
  46. }
  47. - (NSArray<NSDictionary *> *)constructLogEventsArrayLogRecords:
  48. (NSArray<FIRIAMClearcutLogRecord *> *)logRecords {
  49. NSMutableArray<NSDictionary *> *logEvents = [[NSMutableArray alloc] init];
  50. for (id next in logRecords) {
  51. FIRIAMClearcutLogRecord *logRecord = (FIRIAMClearcutLogRecord *)next;
  52. [logEvents addObject:@{
  53. @"event_time_ms" : @((long)logRecord.eventTimestampInSeconds * 1000),
  54. @"source_extension_json" : logRecord.eventExtensionJsonString ?: @""
  55. }];
  56. }
  57. return [logEvents copy];
  58. }
  59. // @return nil if error happened in constructing the body
  60. - (NSDictionary *)constructRequestBodyWithRetryRecords:
  61. (NSArray<FIRIAMClearcutLogRecord *> *)logRecords {
  62. NSMutableDictionary *body = [[NSMutableDictionary alloc] init];
  63. [self updateRequestBodyWithClearcutEnvelopeFields:body];
  64. body[@"log_event"] = [self constructLogEventsArrayLogRecords:logRecords];
  65. return [body copy];
  66. }
  67. // a helper method for dealing with the response received from
  68. // executing NSURLSessionDataTask. Triggers the completion callback accordingly
  69. - (void)handleClearcutAPICallResponseWithData:(NSData *)data
  70. response:(NSURLResponse *)response
  71. error:(NSError *)error
  72. completion:
  73. (nonnull void (^)(BOOL success,
  74. BOOL shouldRetryLogs,
  75. int64_t waitTimeInMills))completion {
  76. if (error) {
  77. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM250003",
  78. @"Internal error: encountered error in uploading clearcut message"
  79. ":%@",
  80. error);
  81. completion(NO, YES, 0);
  82. return;
  83. }
  84. if (![response isKindOfClass:[NSHTTPURLResponse class]]) {
  85. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM250008",
  86. @"Received non http response from sending "
  87. "clearcut requests %@",
  88. response);
  89. completion(NO, YES, 0);
  90. return;
  91. }
  92. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
  93. if (httpResponse.statusCode == 200) {
  94. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM250004",
  95. @"Sending clearcut logging request was successful");
  96. NSError *errorJson = nil;
  97. NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:data
  98. options:kNilOptions
  99. error:&errorJson];
  100. int64_t waitTimeFromClearcutServer = 0;
  101. if (!errorJson && responseDict[@"next_request_wait_millis"]) {
  102. waitTimeFromClearcutServer = [responseDict[@"next_request_wait_millis"] longLongValue];
  103. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM250007",
  104. @"Wait time from clearcut server response is %d seconds",
  105. (int)waitTimeFromClearcutServer / 1000);
  106. }
  107. completion(YES, NO, waitTimeFromClearcutServer);
  108. } else if (httpResponse.statusCode == 400) {
  109. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM250012",
  110. @"Seeing 400 status code in response and we are discarding this log"
  111. @"record");
  112. // 400 means bad request data and it won't be successful with retries. So
  113. // we give up on these log records
  114. completion(NO, NO, 0);
  115. } else {
  116. // May need to handle 401 errors if we do authentication in the future
  117. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM250005",
  118. @"Other http status code seen in clearcut request response %d",
  119. (int)httpResponse.statusCode);
  120. // can be retried
  121. completion(NO, YES, 0);
  122. }
  123. }
  124. - (void)sendClearcutHttpRequestForLogs:(NSArray<FIRIAMClearcutLogRecord *> *)logs
  125. withCompletion:(nonnull void (^)(BOOL success,
  126. BOOL shouldRetryLogs,
  127. int64_t waitTimeInMills))completion {
  128. NSDictionary *requestBody = [self constructRequestBodyWithRetryRecords:logs];
  129. if (!requestBody) {
  130. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM250014",
  131. @"Not able to construct request body for clearcut request, giving up");
  132. completion(NO, NO, 0);
  133. } else {
  134. // sending the log via a http request
  135. NSURLSession *URLSession = [NSURLSession sharedSession];
  136. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
  137. [request setHTTPMethod:@"POST"];
  138. [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
  139. [request addValue:@"application/json" forHTTPHeaderField:@"Accept"];
  140. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM250001",
  141. @"Request body dictionary is %@ for clearcut logging request", requestBody);
  142. NSError *error;
  143. NSData *requestBodyData = [NSJSONSerialization dataWithJSONObject:requestBody
  144. options:0
  145. error:&error];
  146. if (error) {
  147. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM250011",
  148. @"Error in creating request body json for clearcut requests:%@", error);
  149. completion(NO, NO, 0);
  150. return;
  151. }
  152. NSString *requestURLString =
  153. [NSString stringWithFormat:@"https://%@/log?format=json_proto", self.serverHostName];
  154. [request setURL:[NSURL URLWithString:requestURLString]];
  155. [request setHTTPBody:requestBodyData];
  156. NSURLSessionDataTask *clearCutLogDataTask =
  157. [URLSession dataTaskWithRequest:request
  158. completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
  159. [self handleClearcutAPICallResponseWithData:data
  160. response:response
  161. error:error
  162. completion:completion];
  163. }];
  164. if (clearCutLogDataTask == nil) {
  165. NSString *errorDesc = @"Internal error: NSURLSessionDataTask failed to be created due to "
  166. "possibly incorrect parameters";
  167. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM250005", @"%@", errorDesc);
  168. completion(NO, NO, 0);
  169. } else {
  170. [clearCutLogDataTask resume];
  171. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM250002",
  172. @"Making a restful api for sending clearcut logging data with "
  173. "a NSURLSessionDataTask request as %@",
  174. clearCutLogDataTask.currentRequest);
  175. }
  176. }
  177. }
  178. @end