Source/TNLHTTPRequest.m (307 lines of code) (raw):
//
// TNLHTTPRequest.m
// TwitterNetworkLayer
//
// Created on 2/28/15.
// Copyright © 2020 Twitter. All rights reserved.
//
#import "NSDictionary+TNLAdditions.h"
#import "TNL_Project.h"
#import "TNLError.h"
#import "TNLHTTPRequest.h"
#import "TNLRequestConfiguration.h"
NS_ASSUME_NONNULL_BEGIN
#pragma mark - TNLHTTPRequest
@implementation TNLHTTPRequest
{
@protected
NSURL *_URL;
TNLHTTPMethod _HTTPMethodValue;
NSDictionary *_allHTTPHeaderFields;
NSData *_HTTPBody;
NSInputStream *_HTTPBodyStream;
NSString *_HTTPBodyFilePath;
}
@synthesize URL = _URL;
@synthesize HTTPMethodValue = _HTTPMethodValue;
@synthesize allHTTPHeaderFields = _allHTTPHeaderFields;
@synthesize HTTPBody = _HTTPBody;
@synthesize HTTPBodyStream = _HTTPBodyStream;
@synthesize HTTPBodyFilePath = _HTTPBodyFilePath;
#pragma mark init
- (instancetype)init
{
return [self initWithURL:nil];
}
- (instancetype)initWithURL:(nullable NSURL *)url
{
return [self initWithURLRequest:(url) ? [[NSURLRequest alloc] initWithURL:url] : nil];
}
- (instancetype)initWithURLRequest:(nullable NSURLRequest *)request
{
return [self initWithURLRequest:request HTTPBodyFilePath:nil];
}
- (instancetype)initWithURLRequest:(nullable NSURLRequest *)request
HTTPBodyFilePath:(nullable NSString *)bodyFilePath
{
NSString *const HTTPMethod = request.HTTPMethod;
return [self initWithURL:request.URL
HTTPMethodValue:(HTTPMethod) ? TNLHTTPMethodFromString(HTTPMethod) : TNLHTTPMethodGET
HTTPHeaderFields:request.allHTTPHeaderFields
HTTPBody:request.HTTPBody
HTTPBodyStream:request.HTTPBodyStream
HTTPBodyFilePath:bodyFilePath];
}
- (instancetype)initWithURL:(nullable NSURL *)url
HTTPMethodValue:(TNLHTTPMethod)method
HTTPHeaderFields:(nullable NSDictionary *)fields
HTTPBody:(nullable NSData *)body
HTTPBodyStream:(nullable NSInputStream *)bodyStream
HTTPBodyFilePath:(nullable NSString *)bodyFilePath
{
if (self = [super init]) {
_URL = url;
_HTTPMethodValue = method;
_allHTTPHeaderFields = [fields copy];
_HTTPBody = body; // to avoid the cost of memcpy for large bodies, we'll put the responsibility on the caller to not modify the body
_HTTPBodyStream = bodyStream;
_HTTPBodyFilePath = [bodyFilePath copy];
}
return self;
}
#pragma mark NSSecureCoding
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
NSURL *URL = [aDecoder decodeObjectOfClass:[NSURL class]
forKey:@"URL"];
TNLHTTPMethod HTTPMethodValue = [aDecoder decodeIntegerForKey:@"HTTPMethodValue"];
NSDictionary *allHTTPHeaderFields = [[aDecoder decodeObjectOfClass:[NSDictionary class]
forKey:@"allHTTPHeaderFields"] copy];
NSData *HTTPBody = [aDecoder decodeObjectOfClass:[NSData class]
forKey:@"HTTPBody"];
NSString *HTTPBodyFilePath = [aDecoder decodeObjectOfClass:[NSString class]
forKey:@"HTTPBodyFilePath"];
return [self initWithURL:URL
HTTPMethodValue:HTTPMethodValue
HTTPHeaderFields:allHTTPHeaderFields
HTTPBody:HTTPBody
HTTPBodyStream:nil
HTTPBodyFilePath:HTTPBodyFilePath];
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_URL forKey:@"URL"];
[aCoder encodeInteger:_HTTPMethodValue forKey:@"HTTPMethodValue"];
[aCoder encodeObject:_allHTTPHeaderFields forKey:@"allHTTPHeaderFields"];
[aCoder encodeObject:_HTTPBody forKey:@"HTTPBody"];
[aCoder encodeObject:_HTTPBodyFilePath forKey:@"HTTPBodyFilePath"];
if (_HTTPBodyStream) {
TNLAssert(NO && "HTTPBodyStream cannot be encoded!");
// @throw [NSException exceptionWithName:NSInvalidArchiveOperationException reason:[NSString stringWithFormat:@"Cannot encode a %@ with HTTPBodyStream set to anything other than nil!", NSStringFromClass([self class])] userInfo:nil];
}
}
+ (BOOL)supportsSecureCoding
{
return YES;
}
#pragma mark Convenience Constructors
+ (instancetype)HTTPRequestWithRequest:(nullable id<TNLRequest>)request
{
return [[self alloc] initWithURL:[request respondsToSelector:@selector(URL)] ? request.URL : nil
HTTPMethodValue:TNLRequestGetHTTPMethodValue(request)
HTTPHeaderFields:[request respondsToSelector:@selector(allHTTPHeaderFields)] ? request.allHTTPHeaderFields : nil
HTTPBody:[request respondsToSelector:@selector(HTTPBody)] ? request.HTTPBody : nil
HTTPBodyStream:[request respondsToSelector:@selector(HTTPBodyStream)] ? request.HTTPBodyStream : nil
HTTPBodyFilePath:[request respondsToSelector:@selector(HTTPBodyFilePath)] ? request.HTTPBodyFilePath : nil];
}
+ (instancetype)POSTRequestWithURL:(nullable NSURL *)url
HTTPHeaderFields:(nullable NSDictionary *)fields
HTTPBody:(nullable NSData *)body
{
return [[self alloc] initWithURL:url
HTTPMethodValue:TNLHTTPMethodPOST
HTTPHeaderFields:fields
HTTPBody:body
HTTPBodyStream:nil
HTTPBodyFilePath:nil];
}
+ (instancetype)POSTRequestWithURL:(nullable NSURL *)url
HTTPHeaderFields:(nullable NSDictionary *)fields
HTTPBodyStream:(nullable NSInputStream *)bodyStream
{
return [[self alloc] initWithURL:url
HTTPMethodValue:TNLHTTPMethodPOST
HTTPHeaderFields:fields
HTTPBody:nil
HTTPBodyStream:bodyStream
HTTPBodyFilePath:nil];
}
+ (instancetype)POSTRequestWithURL:(nullable NSURL *)url
HTTPHeaderFields:(nullable NSDictionary *)fields
HTTPBodyFilePath:(nullable NSString *)bodyFilePath
{
return [[self alloc] initWithURL:url
HTTPMethodValue:TNLHTTPMethodPOST
HTTPHeaderFields:fields
HTTPBody:nil
HTTPBodyStream:nil
HTTPBodyFilePath:bodyFilePath];
}
+ (instancetype)GETRequestWithURL:(nullable NSURL *)url
HTTPHeaderFields:(nullable NSDictionary *)fields
{
return [[self alloc] initWithURL:url
HTTPMethodValue:TNLHTTPMethodGET
HTTPHeaderFields:fields
HTTPBody:nil
HTTPBodyStream:nil
HTTPBodyFilePath:nil];
}
+ (instancetype)PUTRequestWithURL:(nullable NSURL *)url
HTTPHeaderFields:(nullable NSDictionary *)fields
{
return [[self alloc] initWithURL:url
HTTPMethodValue:TNLHTTPMethodPUT
HTTPHeaderFields:fields
HTTPBody:nil
HTTPBodyStream:nil
HTTPBodyFilePath:nil];
}
+ (instancetype)DELETERequestWithURL:(nullable NSURL *)url
HTTPHeaderFields:(nullable NSDictionary *)fields
{
return [[self alloc] initWithURL:url
HTTPMethodValue:TNLHTTPMethodDELETE
HTTPHeaderFields:fields
HTTPBody:nil
HTTPBodyStream:nil
HTTPBodyFilePath:nil];
}
+ (instancetype)HEADRequestWithURL:(nullable NSURL *)url
HTTPHeaderFields:(nullable NSDictionary *)fields
{
return [[self alloc] initWithURL:url
HTTPMethodValue:TNLHTTPMethodHEAD
HTTPHeaderFields:fields
HTTPBody:nil
HTTPBodyStream:nil
HTTPBodyFilePath:nil];
}
#pragma mark Properties
- (nullable NSURL *)URL
{
return _URL;
}
- (TNLHTTPMethod)HTTPMethodValue
{
return _HTTPMethodValue;
}
- (nullable NSDictionary *)allHTTPHeaderFields
{
return _allHTTPHeaderFields;
}
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field
{
return [self.allHTTPHeaderFields tnl_objectForCaseInsensitiveKey:field];
}
- (nullable NSData *)HTTPBody
{
return _HTTPBody;
}
- (nullable NSInputStream *)HTTPBodyStream
{
return _HTTPBodyStream;
}
- (nullable NSString *)HTTPBodyFilePath
{
return _HTTPBodyFilePath;
}
#pragma mark NSMutableCopying
- (id)copyWithZone:(nullable NSZone *)zone
{
return self;
}
- (id)mutableCopyWithZone:(nullable NSZone *)zone
{
TNLMutableHTTPRequest *copy = [[TNLMutableHTTPRequest allocWithZone:zone] initWithURL:self.URL
HTTPMethodValue:self.HTTPMethodValue
HTTPHeaderFields:_allHTTPHeaderFields
HTTPBody:self.HTTPBody
HTTPBodyStream:self.HTTPBodyStream
HTTPBodyFilePath:self.HTTPBodyFilePath];
return copy;
}
#pragma mark isEqual + hash
- (NSUInteger)hash
{
NSUInteger hash = self.HTTPBody.hash +
self.URL.hash +
(NSUInteger)self.HTTPMethodValue +
self.HTTPBodyFilePath.hash +
self.HTTPBodyStream.hash +
self.allHTTPHeaderFields.count; // use the count of headers, not the hash since the dictionary will be case incensitive in comparison
return hash;
}
- (BOOL)isEqual:(id)object
{
if ([object isKindOfClass:[TNLHTTPRequest class]]) {
return TNLRequestEqualToRequest(self, object, NO /*quickBodyCheck*/);
}
return [super isEqual:object];
}
@end
#pragma mark - TNLMutableHTTPRequest
@implementation TNLMutableHTTPRequest
@dynamic URL;
@dynamic HTTPMethodValue;
@dynamic allHTTPHeaderFields;
@dynamic HTTPBody;
@dynamic HTTPBodyStream;
@dynamic HTTPBodyFilePath;
- (void)setURL:(nullable NSURL *)URL
PROP_RETAIN_ASSIGN_IMP(URL);
- (void)setHTTPMethodValue:(TNLHTTPMethod)HTTPMethodValue
PROP_RETAIN_ASSIGN_IMP(HTTPMethodValue);
- (void)setHTTPBody:(nullable NSData *)HTTPBody
PROP_RETAIN_ASSIGN_IMP(HTTPBody);
- (void)setHTTPBodyStream:(nullable NSInputStream *)HTTPBodyStream
PROP_RETAIN_ASSIGN_IMP(HTTPBodyStream);
- (void)setHTTPBodyFilePath:(nullable NSString *)HTTPBodyFilePath
PROP_COPY_IMP(HTTPBodyFilePath);
- (void)setAllHTTPHeaderFields:(nullable NSDictionary *)allHTTPHeaderFields
{
if (_allHTTPHeaderFields != allHTTPHeaderFields) {
_allHTTPHeaderFields = [allHTTPHeaderFields mutableCopy];
}
}
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field
{
[(NSMutableDictionary *)_allHTTPHeaderFields tnl_setObject:value
forCaseInsensitiveKey:field];
}
- (void)removeAllValuesForHTTPHeaderField:(NSString *)field
{
[(NSMutableDictionary *)_allHTTPHeaderFields tnl_removeObjectsForCaseInsensitiveKey:field];
}
- (id)copyWithZone:(nullable NSZone *)zone
{
TNLHTTPRequest *copy = [[TNLHTTPRequest allocWithZone:zone] initWithURL:self.URL
HTTPMethodValue:self.HTTPMethodValue
HTTPHeaderFields:_allHTTPHeaderFields
HTTPBody:self.HTTPBody
HTTPBodyStream:self.HTTPBodyStream
HTTPBodyFilePath:self.HTTPBodyFilePath];
return copy;
}
- (instancetype)initWithURL:(nullable NSURL *)url
HTTPMethodValue:(TNLHTTPMethod)method
HTTPHeaderFields:(nullable NSDictionary *)fields
HTTPBody:(nullable NSData *)body
HTTPBodyStream:(nullable NSInputStream *)bodyStream
HTTPBodyFilePath:(nullable NSString *)bodyFilePath
{
self = [super initWithURL:url
HTTPMethodValue:method
HTTPHeaderFields:fields
HTTPBody:body
HTTPBodyStream:bodyStream
HTTPBodyFilePath:bodyFilePath];
if (self) {
_allHTTPHeaderFields = [NSMutableDictionary dictionaryWithDictionary:_allHTTPHeaderFields];
}
return self;
}
@end
NS_ASSUME_NONNULL_END