Source/TNLResponse.h (108 lines of code) (raw):

// // TNLResponse.h // TwitterNetworkLayer // // Created on 5/23/14. // Copyright © 2020 Twitter, Inc. All rights reserved. // #import <TwitterNetworkLayer/TNLHTTP.h> #import <TwitterNetworkLayer/TNLRequest.h> NS_ASSUME_NONNULL_BEGIN @protocol TNLTemporaryFile; @class TNLAttemptMetrics; @class TNLResponseMetrics; @class TNLResponseInfo; /** The enum representing the source that the response came from */ typedef NS_ENUM(NSInteger, TNLResponseSource) { /** Unknown */ TNLResponseSourceUnknown = 0, /** The response was retrieved from a local cache */ TNLResponseSourceLocalCache, /** The response was retrieved via a network request */ TNLResponseSourceNetworkRequest }; /** The response object that results from a network request completing. The `TNLResponse` object encapsulates all the response information into a single object. __TNL__ offers powerful response support by permitting callers to provide their own `TNLResponse` subclass `Class` to `TNLRequestOperation` creation. Subclassing is as simple as implementing the `prepare` method and, after calling super, parsing whatever response details are necessary to populate the subclasses properties. __See also__: `TNLResponseInfo` and `TNLResponseMetrics` @warning `[TNLResponse supportsSecureCoding]` returns `YES` even if `originalRequest` does not support secure coding and will encode the `originalRequest` as a `TNLResponseEncodedRequest`. */ @interface TNLResponse : NSObject <NSSecureCoding> { @protected NSError *_operationError; id<TNLRequest> _originalRequest; TNLResponseInfo *_info; TNLResponseMetrics *_metrics; } /** If an error was encountered during the `TNLRequestOperation`, this property will be populated. See `TNLErrorCode` for __TNL__ specific errors. @note An error is ___not___ set when the HTTP Status code represents a client _(4xx)_ or server _(5xx)_ error. Receiving one of those status codes indicates a successful network request with a valid response. Treating client and server error status codes as errors is up to the caller and out of scope for __TNL__. Consider subclassing `TNLResponse` to expose an `HTTPError` property. */ @property (nonatomic, readonly, nullable) NSError *operationError; /** The originating request See `[TNLRequestOperation originalRequest]`. */ @property (nonatomic, readonly, copy, nullable) id<TNLRequest> originalRequest; /** The compilation of response information. See `TNLResponseInfo` */ @property (nonatomic, readonly) TNLResponseInfo *info; /** The compilation of metrics. See `TNLResponseMetrics` */ @property (nonatomic, readonly) TNLResponseMetrics *metrics; /** Constructor - call this to construct a new `TNLResponse` (or construct a subclass) */ + (instancetype)responseWithRequest:(nullable id<TNLRequest>)originalRequest operationError:(nullable NSError *)operationError info:(TNLResponseInfo *)info metrics:(TNLResponseMetrics *)metrics NS_SWIFT_NAME(response(request:operationError:info:metrics:)); /** Unavailable */ - (instancetype)init NS_UNAVAILABLE; /** Unavailable */ + (instancetype)new NS_UNAVAILABLE; /** Constructor to create a new response from an existing response. Useful when creating a subclassed response for extending the `TNLResponse`. @param response the response to create a new response from @return the new response __Do NOT override this method.__ */ + (instancetype)responseWithResponse:(TNLResponse *)response; @end @interface TNLResponse (Protected) /** Method called to prepare the `TNLResponse` (or subclass) after it has initialized. Subclasses SHOULD override this method to customize their construction. CAN set `[TNLAttemptMetrics APIErrors]` and/or `[TNLAttemptMetrics responseBodyParseError]` for the latest attempt in `self.metrics.attemptMetrics`. __Do NOT call this method.__ Be sure to set `CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS` to `YES` in your build settings. */ - (void)prepare NS_REQUIRES_SUPER; /** __Do NOT override this method.__ __Do NOT call this method.__ _Don't even look at it._ Method declaration is present for compiler support to avoid overriding it. Be sure to set `CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS` to `YES` in your build settings. */ - (instancetype)initWithRequest:(nullable id<TNLRequest>)originalRequest operationError:(nullable NSError *)operationError info:(TNLResponseInfo *)info metrics:(TNLResponseMetrics *)metrics __attribute__((deprecated("use +responseWithRequest:operationError:info:metrics: instead"))); @end /** Object encapsulating the response information See `TNLResponse` */ @interface TNLResponseInfo : NSObject <NSSecureCoding> /** The final `NSURLRequest` that was used in the network connection */ @property (nonatomic, readonly, nullable) NSURLRequest *finalURLRequest; /** The final `NSHTTPURLResponse` object that was received by the network connection */ @property (nonatomic, readonly, nullable) NSHTTPURLResponse *URLResponse; /** The response body if _responseDataConsumptionMode_ was set to `TNLResponseDataConsumptionModeStoreInMemory`. See `TNLRequestConfiguration` */ @property (nonatomic, readonly, nullable) NSData *data; /** The temporary file holding the response body if _responseDataConsumptionMode_ was set to `TNLResponseDataConsumptionModeSaveToDisk`. See `TNLRequestConfiguration` and `TNLTemporaryFile` The temporary file will only survive as long as the response object, then will be deleted. Use `[TNLTemporaryFile moveToPath:error:]` on this property to persist the downloaded file. @note the temporary file will not be persisted when using `NSCoding` archival. The unarchived `TNLResponseInfo` will have a `TNLTemporaryFile` that will always fail `[TNLTemporaryFile moveToPath:error:]` since the unarchived response will not own the temporary file. */ @property (nonatomic, readonly, nullable) id<TNLTemporaryFile> temporarySavedFile; /** The source of the response See `TNLResponseSource` */ @property (nonatomic, readonly) TNLResponseSource source; /** Designated initializer */ - (instancetype)initWithFinalURLRequest:(nullable NSURLRequest *)finalURLRequest URLResponse:(nullable NSHTTPURLResponse *)URLResponse source:(TNLResponseSource)source data:(nullable NSData *)data temporarySavedFile:(nullable id<TNLTemporaryFile>)temporarySavedFile NS_DESIGNATED_INITIALIZER; /** Unavailable */ - (instancetype)init NS_UNAVAILABLE; /** Unavailable */ + (instancetype)new NS_UNAVAILABLE; @end /** __TNLResponseInfo (Convenience)__ Convenience methods for `TNLResponseInfo` */ @interface TNLResponseInfo (Convenience) /** Same as _URLResponse.statusCode_ */ @property (nonatomic, readonly) TNLHTTPStatusCode statusCode; /** Same as _URLResponse.URL ?: _finalURLRequest.URL_ */ @property (nonatomic, readonly, nullable) NSURL *finalURL; /** Same as _URLResponse.allHTTPHeaderFields_ */ @property (nonatomic, readonly, nullable) NSDictionary<NSString *, NSString *> *allHTTPHeaderFields; /** Return the value of the response header field, using headerField as a case-insensitive key. */ - (nullable NSString *)valueForResponseHeaderField:(NSString *)headerField; /** Returns a copy of the _allHTTPHeaderFields_ with only lowercase keys */ - (nullable NSDictionary<NSString *, NSString *> *)allHTTPHeaderFieldsWithLowerCaseKeys; @end /** __TNLResponseInfo (RetryAfter)__ Convenience methods gathering Retry-After information from `TNLResponseInfo` */ @interface TNLResponseInfo (RetryAfter) /** Convenience method for determining if there is a Retry-After header */ @property (nonatomic, readonly) BOOL hasRetryAfterHeader; /** Convenience method for retrieving the Retry-After raw value */ @property (nonatomic, readonly, nullable) NSString *retryAfterRawValue; /** Convenience method for retrieving the Retry-After date value as an `NSDate`. Returns `nil` if value could not be parsed */ @property (nonatomic, readonly, nullable) NSDate *retryAfterDate; /** Convenience method for retrieving the Retry-After delay value in seconds from the now. Returns `NSTimeIntervalSince1970` if value could not be parsed. Value can be negative if it occurs in the past. */ - (NSTimeInterval)retryAfterDelayFromNow; @end /** Converted request for Secure Coding */ @interface TNLResponseEncodedRequest : NSObject <TNLRequest, NSSecureCoding> /** The name of the source `TNLRequest` class that was encoded */ @property (nonatomic, readonly, copy, nullable) NSString *encodedSourceRequestClassName; /** If the encoded source request has a body */ @property (nonatomic, readonly) BOOL encodedSourceRequestHadBody; /** Unavailable */ - (instancetype)init NS_UNAVAILABLE; /** Unavailable */ + (instancetype)new NS_UNAVAILABLE; @end /** Object encapsulating the metrics related the to request operation The `TNLResponseMetrics` object encapsulates all the execution metrics and meta-data of finished (completed, cancelled or failed) `TNLRequestOperation`. The object itself offers macro insight such as the number of _HTTP_ attempts that occurred in the operation (_attemptCount_) and the total duration of the operation (_totalDuration_). At a more micro level, the `TNLAttemptMetrics` can be accessed via the _attemptMetrics_ property. As you are inspecting attempt metrics, the meta-data of the attempt may also be of use and can be accessed from `[TNLAttemptMetrics metaData]` as `TNLAttemptMetaData`. See also `TNLResponse` */ @interface TNLResponseMetrics : NSObject <NSSecureCoding> /** When the `TNLRequestOperation` was enqueued to its `TNLRequestOperationQueue` as `NSDate` */ @property (nonatomic, readonly) NSDate *enqueueDate; /** When the `TNLRequestOperation` was enqueued to its `TNLRequestOperationQueue` */ @property (nonatomic, readonly) uint64_t enqueueMachTime __attribute__((deprecated("use enqueueDate instead"))); /** When the `TNLRequestOperation` completed as `NSDate` */ @property (nonatomic, readonly, nullable) NSDate *completeDate; /** When the `TNLRequestOperation` completed */ @property (nonatomic, readonly) uint64_t completeMachTime __attribute__((deprecated("use completeDate instead"))); /** The number of attempts that occurred (initial attempt + retries + redirects) */ @property (nonatomic, readonly) NSUInteger attemptCount; /** The number of retries that occurred */ @property (nonatomic, readonly) NSUInteger retryCount; /** The number of redirects that occurred */ @property (nonatomic, readonly) NSUInteger redirectCount; /** The underlying attempt metrics as `TNLAttemptMetrics` objects */ @property (nonatomic, readonly, nullable) NSArray<TNLAttemptMetrics *> *attemptMetrics; /** A description of the response metrics as a serializable dictionary object */ - (NSDictionary *)dictionaryDescription:(BOOL)verbose; /** Helper init for custom `TNLResponseMetrics` */ - (instancetype)initWithEnqueueDate:(NSDate *)enqueueDate enqueueTime:(uint64_t)enqueueTime completeDate:(nullable NSDate *)completeDate completeTime:(uint64_t)completeTime attemptMetrics:(nullable NSArray<TNLAttemptMetrics *> *)attemptMetrics; @end /** Convenience methods on `TNLResponseMetrics` */ @interface TNLResponseMetrics (Convenience) /** When the first attempt started (after the time spent waiting in the queue) as `NSDate` */ @property (nonatomic, readonly, nullable) NSDate *firstAttemptStartDate; /** When the first attempt started (after the time spent waiting in the queue) */ @property (nonatomic, readonly) uint64_t firstAttemptStartMachTime __attribute__((deprecated("use firstAttemptStartDate instead"))); /** When the current attempt started as `NSDate` */ @property (nonatomic, readonly, nullable) NSDate *currentAttemptStartDate; /** When the current attempt started */ @property (nonatomic, readonly) uint64_t currentAttemptStartMachTime __attribute__((deprecated("use currentAttemptStartDate instead"))); /** When the current attempt ended as `NSDate` */ @property (nonatomic, readonly, nullable) NSDate *currentAttemptEndDate; /** When the current attempt ended */ @property (nonatomic, readonly) uint64_t currentAttemptEndMachTime __attribute__((deprecated("use currentAttemptEndDate instead"))); /** calculate the total duration of the operation */ - (NSTimeInterval)totalDuration; /** calculate the duration that operation was in the queue but not yet started */ - (NSTimeInterval)queuedDuration; /** calculate the duration the operation took to execute upon all attempts (including retries). This excludes queued time but does include time spent waiting to retry. */ - (NSTimeInterval)allAttemptsDuration; /** calculate the duration that the current attempt took */ - (NSTimeInterval)currentAttemptDuration; @end /** Convenience methods that are useful for testing `TNLResponseMetrics`. Should not be used for production code. */ @interface TNLResponseMetrics (UnitTesting) /** Construct a `TNLResponseMetrics` instance from the provided arguments. This will be a fake set of metrics since this will not hydrate the metrics with the natural mechanism encapsulated by `TNLRequestOperation`. @param duration The `totalDuration` of the metrics. `firstAttemptStartMachTime` will be `0`. @param request The `NSURLRequest` for the encapsulated attempt @param URLResponse The `NSHTTPURLResponse` for the encapsulated attempt @param error The `NSError` for the encapsulated attempt @return a fake `TNLResponseMetrics` object with a single attempt being encapsulated. */ + (instancetype)fakeMetricsForDuration:(NSTimeInterval)duration URLRequest:(NSURLRequest *)request URLResponse:(nullable NSHTTPURLResponse *)URLResponse operationError:(nullable NSError *)error; @end NS_ASSUME_NONNULL_END