FLEXNetworkTransactionDetailTableViewController.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. //
  2. // FLEXNetworkTransactionDetailTableViewController.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 2/10/15.
  6. // Copyright (c) 2015 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXColor.h"
  9. #import "FLEXNetworkTransactionDetailTableViewController.h"
  10. #import "FLEXNetworkCurlLogger.h"
  11. #import "FLEXNetworkRecorder.h"
  12. #import "FLEXNetworkTransaction.h"
  13. #import "FLEXWebViewController.h"
  14. #import "FLEXImagePreviewViewController.h"
  15. #import "FLEXMultilineTableViewCell.h"
  16. #import "FLEXUtility.h"
  17. #import "FLEXManager+Private.h"
  18. typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
  19. @interface FLEXNetworkDetailRow : NSObject
  20. @property (nonatomic, copy) NSString *title;
  21. @property (nonatomic, copy) NSString *detailText;
  22. @property (nonatomic, copy) FLEXNetworkDetailRowSelectionFuture selectionFuture;
  23. @end
  24. @implementation FLEXNetworkDetailRow
  25. @end
  26. @interface FLEXNetworkDetailSection : NSObject
  27. @property (nonatomic, copy) NSString *title;
  28. @property (nonatomic, copy) NSArray<FLEXNetworkDetailRow *> *rows;
  29. @end
  30. @implementation FLEXNetworkDetailSection
  31. @end
  32. @interface FLEXNetworkTransactionDetailTableViewController ()
  33. @property (nonatomic, copy) NSArray<FLEXNetworkDetailSection *> *sections;
  34. @end
  35. @implementation FLEXNetworkTransactionDetailTableViewController
  36. - (instancetype)initWithStyle:(UITableViewStyle)style
  37. {
  38. // Force grouped style
  39. self = [super initWithStyle:UITableViewStyleGrouped];
  40. if (self) {
  41. [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleTransactionUpdatedNotification:) name:kFLEXNetworkRecorderTransactionUpdatedNotification object:nil];
  42. self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Copy curl" style:UIBarButtonItemStylePlain target:self action:@selector(copyButtonPressed:)];
  43. }
  44. return self;
  45. }
  46. - (void)viewDidLoad
  47. {
  48. [super viewDidLoad];
  49. [self.tableView registerClass:[FLEXMultilineTableViewCell class] forCellReuseIdentifier:kFLEXMultilineTableViewCellIdentifier];
  50. }
  51. - (void)setTransaction:(FLEXNetworkTransaction *)transaction
  52. {
  53. if (![_transaction isEqual:transaction]) {
  54. _transaction = transaction;
  55. self.title = [transaction.request.URL lastPathComponent];
  56. [self rebuildTableSections];
  57. }
  58. }
  59. - (void)setSections:(NSArray<FLEXNetworkDetailSection *> *)sections
  60. {
  61. if (![_sections isEqual:sections]) {
  62. _sections = [sections copy];
  63. [self.tableView reloadData];
  64. }
  65. }
  66. - (void)rebuildTableSections
  67. {
  68. NSMutableArray<FLEXNetworkDetailSection *> *sections = [NSMutableArray array];
  69. FLEXNetworkDetailSection *generalSection = [[self class] generalSectionForTransaction:self.transaction];
  70. if (generalSection.rows.count > 0) {
  71. [sections addObject:generalSection];
  72. }
  73. FLEXNetworkDetailSection *requestHeadersSection = [[self class] requestHeadersSectionForTransaction:self.transaction];
  74. if (requestHeadersSection.rows.count > 0) {
  75. [sections addObject:requestHeadersSection];
  76. }
  77. FLEXNetworkDetailSection *queryParametersSection = [[self class] queryParametersSectionForTransaction:self.transaction];
  78. if (queryParametersSection.rows.count > 0) {
  79. [sections addObject:queryParametersSection];
  80. }
  81. FLEXNetworkDetailSection *postBodySection = [[self class] postBodySectionForTransaction:self.transaction];
  82. if (postBodySection.rows.count > 0) {
  83. [sections addObject:postBodySection];
  84. }
  85. FLEXNetworkDetailSection *responseHeadersSection = [[self class] responseHeadersSectionForTransaction:self.transaction];
  86. if (responseHeadersSection.rows.count > 0) {
  87. [sections addObject:responseHeadersSection];
  88. }
  89. self.sections = sections;
  90. }
  91. - (void)handleTransactionUpdatedNotification:(NSNotification *)notification
  92. {
  93. FLEXNetworkTransaction *transaction = [[notification userInfo] objectForKey:kFLEXNetworkRecorderUserInfoTransactionKey];
  94. if (transaction == self.transaction) {
  95. [self rebuildTableSections];
  96. }
  97. }
  98. - (void)copyButtonPressed:(id)sender
  99. {
  100. [UIPasteboard.generalPasteboard setString:[FLEXNetworkCurlLogger curlCommandString:_transaction.request]];
  101. }
  102. #pragma mark - Table view data source
  103. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  104. {
  105. return self.sections.count;
  106. }
  107. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  108. {
  109. FLEXNetworkDetailSection *sectionModel = self.sections[section];
  110. return sectionModel.rows.count;
  111. }
  112. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
  113. {
  114. FLEXNetworkDetailSection *sectionModel = self.sections[section];
  115. return sectionModel.title;
  116. }
  117. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  118. {
  119. FLEXMultilineTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXMultilineTableViewCellIdentifier forIndexPath:indexPath];
  120. FLEXNetworkDetailRow *rowModel = [self rowModelAtIndexPath:indexPath];
  121. cell.textLabel.attributedText = [[self class] attributedTextForRow:rowModel];
  122. cell.accessoryType = rowModel.selectionFuture ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
  123. cell.selectionStyle = rowModel.selectionFuture ? UITableViewCellSelectionStyleDefault : UITableViewCellSelectionStyleNone;
  124. return cell;
  125. }
  126. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  127. {
  128. FLEXNetworkDetailRow *rowModel = [self rowModelAtIndexPath:indexPath];
  129. UIViewController *viewController = nil;
  130. if (rowModel.selectionFuture) {
  131. viewController = rowModel.selectionFuture();
  132. }
  133. if ([viewController isKindOfClass:UIAlertController.class]) {
  134. [self presentViewController:viewController animated:YES completion:nil];
  135. } else if (viewController) {
  136. [self.navigationController pushViewController:viewController animated:YES];
  137. }
  138. [tableView deselectRowAtIndexPath:indexPath animated:YES];
  139. }
  140. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  141. {
  142. FLEXNetworkDetailRow *row = [self rowModelAtIndexPath:indexPath];
  143. NSAttributedString *attributedText = [[self class] attributedTextForRow:row];
  144. BOOL showsAccessory = row.selectionFuture != nil;
  145. return [FLEXMultilineTableViewCell preferredHeightWithAttributedText:attributedText inTableViewWidth:self.tableView.bounds.size.width style:UITableViewStyleGrouped showsAccessory:showsAccessory];
  146. }
  147. - (FLEXNetworkDetailRow *)rowModelAtIndexPath:(NSIndexPath *)indexPath
  148. {
  149. FLEXNetworkDetailSection *sectionModel = self.sections[indexPath.section];
  150. return sectionModel.rows[indexPath.row];
  151. }
  152. #pragma mark - Cell Copying
  153. - (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
  154. {
  155. return YES;
  156. }
  157. - (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
  158. {
  159. return action == @selector(copy:);
  160. }
  161. - (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
  162. {
  163. if (action == @selector(copy:)) {
  164. FLEXNetworkDetailRow *row = [self rowModelAtIndexPath:indexPath];
  165. [UIPasteboard.generalPasteboard setString:row.detailText];
  166. }
  167. }
  168. #pragma mark - View Configuration
  169. + (NSAttributedString *)attributedTextForRow:(FLEXNetworkDetailRow *)row
  170. {
  171. NSDictionary<NSString *, id> *titleAttributes = @{ NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:12.0],
  172. NSForegroundColorAttributeName : [UIColor colorWithWhite:0.5 alpha:1.0] };
  173. NSDictionary<NSString *, id> *detailAttributes = @{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont],
  174. NSForegroundColorAttributeName : [FLEXColor primaryTextColor] };
  175. NSString *title = [NSString stringWithFormat:@"%@: ", row.title];
  176. NSString *detailText = row.detailText ?: @"";
  177. NSMutableAttributedString *attributedText = [NSMutableAttributedString new];
  178. [attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:title attributes:titleAttributes]];
  179. [attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:detailText attributes:detailAttributes]];
  180. return attributedText;
  181. }
  182. #pragma mark - Table Data Generation
  183. + (FLEXNetworkDetailSection *)generalSectionForTransaction:(FLEXNetworkTransaction *)transaction
  184. {
  185. NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray array];
  186. FLEXNetworkDetailRow *requestURLRow = [FLEXNetworkDetailRow new];
  187. requestURLRow.title = @"Request URL";
  188. NSURL *url = transaction.request.URL;
  189. requestURLRow.detailText = url.absoluteString;
  190. requestURLRow.selectionFuture = ^{
  191. UIViewController *urlWebViewController = [[FLEXWebViewController alloc] initWithURL:url];
  192. urlWebViewController.title = url.absoluteString;
  193. return urlWebViewController;
  194. };
  195. [rows addObject:requestURLRow];
  196. FLEXNetworkDetailRow *requestMethodRow = [FLEXNetworkDetailRow new];
  197. requestMethodRow.title = @"Request Method";
  198. requestMethodRow.detailText = transaction.request.HTTPMethod;
  199. [rows addObject:requestMethodRow];
  200. if (transaction.cachedRequestBody.length > 0) {
  201. FLEXNetworkDetailRow *postBodySizeRow = [FLEXNetworkDetailRow new];
  202. postBodySizeRow.title = @"Request Body Size";
  203. postBodySizeRow.detailText = [NSByteCountFormatter stringFromByteCount:transaction.cachedRequestBody.length countStyle:NSByteCountFormatterCountStyleBinary];
  204. [rows addObject:postBodySizeRow];
  205. FLEXNetworkDetailRow *postBodyRow = [FLEXNetworkDetailRow new];
  206. postBodyRow.title = @"Request Body";
  207. postBodyRow.detailText = @"tap to view";
  208. postBodyRow.selectionFuture = ^UIViewController * (void){
  209. NSString *contentType = [transaction.request valueForHTTPHeaderField:@"Content-Type"];
  210. UIViewController *detailViewController = [self detailViewControllerForMIMEType:contentType data:[self postBodyDataForTransaction:transaction]];
  211. if (detailViewController) {
  212. detailViewController.title = @"Request Body";
  213. return detailViewController;
  214. }
  215. NSString *alertMessage = [NSString stringWithFormat:@"FLEX does not have a viewer for request body data with MIME type: %@", [transaction.request valueForHTTPHeaderField:@"Content-Type"]];
  216. UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Can't View Body Data"
  217. message:alertMessage
  218. preferredStyle:UIAlertControllerStyleAlert];
  219. [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
  220. return alert;
  221. };
  222. [rows addObject:postBodyRow];
  223. }
  224. NSString *statusCodeString = [FLEXUtility statusCodeStringFromURLResponse:transaction.response];
  225. if (statusCodeString.length > 0) {
  226. FLEXNetworkDetailRow *statusCodeRow = [FLEXNetworkDetailRow new];
  227. statusCodeRow.title = @"Status Code";
  228. statusCodeRow.detailText = statusCodeString;
  229. [rows addObject:statusCodeRow];
  230. }
  231. if (transaction.error) {
  232. FLEXNetworkDetailRow *errorRow = [FLEXNetworkDetailRow new];
  233. errorRow.title = @"Error";
  234. errorRow.detailText = transaction.error.localizedDescription;
  235. [rows addObject:errorRow];
  236. }
  237. FLEXNetworkDetailRow *responseBodyRow = [FLEXNetworkDetailRow new];
  238. responseBodyRow.title = @"Response Body";
  239. NSData *responseData = [[FLEXNetworkRecorder defaultRecorder] cachedResponseBodyForTransaction:transaction];
  240. if (responseData.length > 0) {
  241. responseBodyRow.detailText = @"tap to view";
  242. // Avoid a long lived strong reference to the response data in case we need to purge it from the cache.
  243. __weak NSData *weakResponseData = responseData;
  244. responseBodyRow.selectionFuture = ^UIViewController * (void){
  245. NSString *alertMessage = nil;
  246. NSData *strongResponseData = weakResponseData;
  247. if (strongResponseData) {
  248. UIViewController *responseBodyDetailViewController = [self detailViewControllerForMIMEType:transaction.response.MIMEType data:strongResponseData];
  249. if (responseBodyDetailViewController) {
  250. responseBodyDetailViewController.title = @"Response";
  251. return responseBodyDetailViewController;
  252. }
  253. alertMessage = [NSString stringWithFormat:@"FLEX does not have a viewer for responses with MIME type: %@", transaction.response.MIMEType];
  254. } else {
  255. alertMessage = @"The response has been purged from the cache";
  256. }
  257. UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Can't View Response"
  258. message:alertMessage
  259. preferredStyle:UIAlertControllerStyleAlert];
  260. [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
  261. return alert;
  262. };
  263. } else {
  264. BOOL emptyResponse = transaction.receivedDataLength == 0;
  265. responseBodyRow.detailText = emptyResponse ? @"empty" : @"not in cache";
  266. }
  267. [rows addObject:responseBodyRow];
  268. FLEXNetworkDetailRow *responseSizeRow = [FLEXNetworkDetailRow new];
  269. responseSizeRow.title = @"Response Size";
  270. responseSizeRow.detailText = [NSByteCountFormatter stringFromByteCount:transaction.receivedDataLength countStyle:NSByteCountFormatterCountStyleBinary];
  271. [rows addObject:responseSizeRow];
  272. FLEXNetworkDetailRow *mimeTypeRow = [FLEXNetworkDetailRow new];
  273. mimeTypeRow.title = @"MIME Type";
  274. mimeTypeRow.detailText = transaction.response.MIMEType;
  275. [rows addObject:mimeTypeRow];
  276. FLEXNetworkDetailRow *mechanismRow = [FLEXNetworkDetailRow new];
  277. mechanismRow.title = @"Mechanism";
  278. mechanismRow.detailText = transaction.requestMechanism;
  279. [rows addObject:mechanismRow];
  280. NSDateFormatter *startTimeFormatter = [NSDateFormatter new];
  281. startTimeFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS";
  282. FLEXNetworkDetailRow *localStartTimeRow = [FLEXNetworkDetailRow new];
  283. localStartTimeRow.title = [NSString stringWithFormat:@"Start Time (%@)", [[NSTimeZone localTimeZone] abbreviationForDate:transaction.startTime]];
  284. localStartTimeRow.detailText = [startTimeFormatter stringFromDate:transaction.startTime];
  285. [rows addObject:localStartTimeRow];
  286. startTimeFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
  287. FLEXNetworkDetailRow *utcStartTimeRow = [FLEXNetworkDetailRow new];
  288. utcStartTimeRow.title = @"Start Time (UTC)";
  289. utcStartTimeRow.detailText = [startTimeFormatter stringFromDate:transaction.startTime];
  290. [rows addObject:utcStartTimeRow];
  291. FLEXNetworkDetailRow *unixStartTime = [FLEXNetworkDetailRow new];
  292. unixStartTime.title = @"Unix Start Time";
  293. unixStartTime.detailText = [NSString stringWithFormat:@"%f", [transaction.startTime timeIntervalSince1970]];
  294. [rows addObject:unixStartTime];
  295. FLEXNetworkDetailRow *durationRow = [FLEXNetworkDetailRow new];
  296. durationRow.title = @"Total Duration";
  297. durationRow.detailText = [FLEXUtility stringFromRequestDuration:transaction.duration];
  298. [rows addObject:durationRow];
  299. FLEXNetworkDetailRow *latencyRow = [FLEXNetworkDetailRow new];
  300. latencyRow.title = @"Latency";
  301. latencyRow.detailText = [FLEXUtility stringFromRequestDuration:transaction.latency];
  302. [rows addObject:latencyRow];
  303. FLEXNetworkDetailSection *generalSection = [FLEXNetworkDetailSection new];
  304. generalSection.title = @"General";
  305. generalSection.rows = rows;
  306. return generalSection;
  307. }
  308. + (FLEXNetworkDetailSection *)requestHeadersSectionForTransaction:(FLEXNetworkTransaction *)transaction
  309. {
  310. FLEXNetworkDetailSection *requestHeadersSection = [FLEXNetworkDetailSection new];
  311. requestHeadersSection.title = @"Request Headers";
  312. requestHeadersSection.rows = [self networkDetailRowsFromDictionary:transaction.request.allHTTPHeaderFields];
  313. return requestHeadersSection;
  314. }
  315. + (FLEXNetworkDetailSection *)postBodySectionForTransaction:(FLEXNetworkTransaction *)transaction
  316. {
  317. FLEXNetworkDetailSection *postBodySection = [FLEXNetworkDetailSection new];
  318. postBodySection.title = @"Request Body Parameters";
  319. if (transaction.cachedRequestBody.length > 0) {
  320. NSString *contentType = [transaction.request valueForHTTPHeaderField:@"Content-Type"];
  321. if ([contentType hasPrefix:@"application/x-www-form-urlencoded"]) {
  322. NSString *bodyString = [NSString stringWithCString:[self postBodyDataForTransaction:transaction].bytes encoding:NSUTF8StringEncoding];
  323. postBodySection.rows = [self networkDetailRowsFromDictionary:[FLEXUtility dictionaryFromQuery:bodyString]];
  324. }
  325. }
  326. return postBodySection;
  327. }
  328. + (FLEXNetworkDetailSection *)queryParametersSectionForTransaction:(FLEXNetworkTransaction *)transaction
  329. {
  330. NSDictionary<NSString *, id> *queryDictionary = [FLEXUtility dictionaryFromQuery:transaction.request.URL.query];
  331. FLEXNetworkDetailSection *querySection = [FLEXNetworkDetailSection new];
  332. querySection.title = @"Query Parameters";
  333. querySection.rows = [self networkDetailRowsFromDictionary:queryDictionary];
  334. return querySection;
  335. }
  336. + (FLEXNetworkDetailSection *)responseHeadersSectionForTransaction:(FLEXNetworkTransaction *)transaction
  337. {
  338. FLEXNetworkDetailSection *responseHeadersSection = [FLEXNetworkDetailSection new];
  339. responseHeadersSection.title = @"Response Headers";
  340. if ([transaction.response isKindOfClass:[NSHTTPURLResponse class]]) {
  341. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)transaction.response;
  342. responseHeadersSection.rows = [self networkDetailRowsFromDictionary:httpResponse.allHeaderFields];
  343. }
  344. return responseHeadersSection;
  345. }
  346. + (NSArray<FLEXNetworkDetailRow *> *)networkDetailRowsFromDictionary:(NSDictionary<NSString *, id> *)dictionary
  347. {
  348. NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray arrayWithCapacity:dictionary.count];
  349. NSArray<NSString *> *sortedKeys = [dictionary.allKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
  350. for (NSString *key in sortedKeys) {
  351. id value = dictionary[key];
  352. FLEXNetworkDetailRow *row = [FLEXNetworkDetailRow new];
  353. row.title = key;
  354. row.detailText = [value description];
  355. [rows addObject:row];
  356. }
  357. return [rows copy];
  358. }
  359. + (UIViewController *)detailViewControllerForMIMEType:(NSString *)mimeType data:(NSData *)data
  360. {
  361. FLEXCustomContentViewerFuture makeCustomViewer = [FLEXManager sharedManager].customContentTypeViewers[mimeType.lowercaseString];
  362. if (makeCustomViewer) {
  363. UIViewController *viewer = makeCustomViewer(data);
  364. if (viewer) {
  365. return viewer;
  366. }
  367. }
  368. // FIXME (RKO): Don't rely on UTF8 string encoding
  369. UIViewController *detailViewController = nil;
  370. if ([FLEXUtility isValidJSONData:data]) {
  371. NSString *prettyJSON = [FLEXUtility prettyJSONStringFromData:data];
  372. if (prettyJSON.length > 0) {
  373. detailViewController = [[FLEXWebViewController alloc] initWithText:prettyJSON];
  374. }
  375. } else if ([mimeType hasPrefix:@"image/"]) {
  376. UIImage *image = [UIImage imageWithData:data];
  377. detailViewController = [[FLEXImagePreviewViewController alloc] initWithImage:image];
  378. } else if ([mimeType isEqual:@"application/x-plist"]) {
  379. id propertyList = [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:NULL];
  380. detailViewController = [[FLEXWebViewController alloc] initWithText:[propertyList description]];
  381. }
  382. // Fall back to trying to show the response as text
  383. if (!detailViewController) {
  384. NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  385. if (text.length > 0) {
  386. detailViewController = [[FLEXWebViewController alloc] initWithText:text];
  387. }
  388. }
  389. return detailViewController;
  390. }
  391. + (NSData *)postBodyDataForTransaction:(FLEXNetworkTransaction *)transaction
  392. {
  393. NSData *bodyData = transaction.cachedRequestBody;
  394. if (bodyData.length > 0) {
  395. NSString *contentEncoding = [transaction.request valueForHTTPHeaderField:@"Content-Encoding"];
  396. if ([contentEncoding rangeOfString:@"deflate" options:NSCaseInsensitiveSearch].length > 0 || [contentEncoding rangeOfString:@"gzip" options:NSCaseInsensitiveSearch].length > 0) {
  397. bodyData = [FLEXUtility inflatedDataFromCompressedData:bodyData];
  398. }
  399. }
  400. return bodyData;
  401. }
  402. @end