-
Notifications
You must be signed in to change notification settings - Fork 333
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor HTTP code and add proxy support. (#578)
This refactors the HTTP interaction in the library + Segment integration. Internally, it moves away from `NSURLConnection` to `NSURLSession`. `SEGAnalyticsRequest` is replaced by `NSURLSessionTask`, and a thin wrapper class `SEGHTTPClient` is introduced to simplify the interaction between the library and the Segment API. This also adds tests for the networking code by stubbing the Segment API using https://github.com/luisobo/Nocilla so the behaviour of `SEGHTTPClient` can be verified under different responses from the server. Publicly, it also adds a `SEGRequestFactory` abstraction that given a URL returns a `NSMutableURLRequest `. The default implementation simply calls `[NSMutableURLRequest requestWithURL:url]`. It is configurable, and this lets users customize the `NSMutableURLRequest` made by the library. e.g. users could change it to point the SDK to a custom proxy instance.
- Loading branch information
Showing
90 changed files
with
3,299 additions
and
999 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#import <Foundation/Foundation.h> | ||
#import "SEGAnalytics.h" | ||
|
||
|
||
@interface SEGHTTPClient : NSObject | ||
|
||
@property (nonatomic, strong) SEGRequestFactory requestFactory; | ||
|
||
+ (SEGRequestFactory)defaultRequestFactory; | ||
|
||
- (instancetype)initWithRequestFactory:(SEGRequestFactory)requestFactory; | ||
|
||
/** | ||
* Upload dictionary formatted as per https://segment.com/docs/sources/server/http/#batch. | ||
* This method will convert the dictionary to json, gzip it and upload the data. | ||
* It will respond with retry = YES if the batch should be reuploaded at a later time. | ||
* It will ask to retry for json errors and 3xx/5xx codes, and not retry for 2xx/4xx response codes. | ||
*/ | ||
- (NSURLSessionUploadTask *)upload:(NSDictionary *)batch forWriteKey:(NSString *)writeKey completionHandler:(void (^)(BOOL retry))completionHandler; | ||
|
||
- (NSURLSessionDataTask *)settingsForWriteKey:(NSString *)writeKey completionHandler:(void (^)(BOOL success, NSDictionary *settings))completionHandler; | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
#import "SEGHTTPClient.h" | ||
#import "NSData+GZIP.h" | ||
#import "SEGAnalyticsUtils.h" | ||
|
||
|
||
@implementation SEGHTTPClient | ||
|
||
+ (NSMutableURLRequest * (^)(NSURL *))defaultRequestFactory | ||
{ | ||
return ^(NSURL *url) { | ||
return [NSMutableURLRequest requestWithURL:url]; | ||
}; | ||
} | ||
|
||
- (instancetype)initWithRequestFactory:(SEGRequestFactory)requestFactory | ||
{ | ||
if (self = [self init]) { | ||
if (requestFactory == nil) { | ||
self.requestFactory = [SEGHTTPClient defaultRequestFactory]; | ||
} else { | ||
self.requestFactory = requestFactory; | ||
} | ||
} | ||
return self; | ||
} | ||
|
||
- (NSString *)authorizationHeader:(NSString *)writeKey | ||
{ | ||
NSString *rawHeader = [writeKey stringByAppendingString:@":"]; | ||
NSData *userPasswordData = [rawHeader dataUsingEncoding:NSUTF8StringEncoding]; | ||
return [userPasswordData base64EncodedStringWithOptions:0]; | ||
} | ||
|
||
- (NSURLSessionUploadTask *)upload:(NSDictionary *)batch forWriteKey:(NSString *)writeKey completionHandler:(void (^)(BOOL retry))completionHandler | ||
{ | ||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; | ||
config.HTTPAdditionalHeaders = @{ | ||
@"Accept-Encoding" : @"gzip", | ||
@"Content-Encoding" : @"gzip", | ||
@"Content-Type" : @"application/json", | ||
@"Authorization" : [@"Basic " stringByAppendingString:[self authorizationHeader:writeKey]], | ||
}; | ||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; | ||
|
||
NSURL *url = [NSURL URLWithString:@"https://api.segment.io/v1/batch"]; | ||
NSMutableURLRequest *request = self.requestFactory(url); | ||
[request setHTTPMethod:@"POST"]; | ||
|
||
NSError *error = nil; | ||
NSException *exception = nil; | ||
NSData *payload = nil; | ||
@try { | ||
payload = [NSJSONSerialization dataWithJSONObject:batch options:0 error:&error]; | ||
} | ||
@catch (NSException *exc) { | ||
exception = exc; | ||
} | ||
if (error || exception) { | ||
SEGLog(@"%@ Error serializing JSON for batch upload %@", self, error); | ||
completionHandler(NO); // Don't retry this batch. | ||
return nil; | ||
} | ||
NSData *gzippedPayload = [payload seg_gzippedData]; | ||
|
||
NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromData:gzippedPayload completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { | ||
if (error) { | ||
SEGLog(@"Error uploading request %@.", error); | ||
completionHandler(YES); | ||
return; | ||
} | ||
|
||
NSInteger code = ((NSHTTPURLResponse *)response).statusCode; | ||
if (code < 300) { | ||
// 2xx response codes. | ||
completionHandler(NO); | ||
return; | ||
} | ||
if (code < 400) { | ||
// 3xx response codes. | ||
SEGLog(@"Server responded with unexpected HTTP code %d.", code); | ||
completionHandler(YES); | ||
return; | ||
} | ||
if (code < 500) { | ||
// 4xx response codes. | ||
SEGLog(@"Server rejected payload with HTTP code %d.", code); | ||
completionHandler(NO); | ||
return; | ||
} | ||
|
||
// 5xx response codes. | ||
SEGLog(@"Server error with HTTP code %d.", code); | ||
completionHandler(YES); | ||
}]; | ||
[task resume]; | ||
return task; | ||
} | ||
|
||
- (NSURLSessionDataTask *)settingsForWriteKey:(NSString *)writeKey completionHandler:(void (^)(BOOL success, NSDictionary *settings))completionHandler | ||
{ | ||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; | ||
config.HTTPAdditionalHeaders = @{ | ||
@"Accept-Encoding" : @"gzip" | ||
}; | ||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; | ||
|
||
NSString *rawURL = [NSString stringWithFormat:@"https://cdn.segment.com/v1/projects/%@/settings", writeKey]; | ||
NSURL *url = [NSURL URLWithString:rawURL]; | ||
NSMutableURLRequest *request = self.requestFactory(url); | ||
[request setHTTPMethod:@"GET"]; | ||
|
||
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { | ||
if (error != nil) { | ||
SEGLog(@"Error fetching settings %@.", error); | ||
completionHandler(NO, nil); | ||
return; | ||
} | ||
|
||
NSInteger code = ((NSHTTPURLResponse *)response).statusCode; | ||
if (code > 300) { | ||
SEGLog(@"Server responded with unexpected HTTP code %d.", code); | ||
completionHandler(NO, nil); | ||
return; | ||
} | ||
|
||
NSError *jsonError = nil; | ||
id responseJson = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; | ||
if (jsonError != nil) { | ||
SEGLog(@"Error deserializing response body %@.", jsonError); | ||
completionHandler(NO, nil); | ||
return; | ||
} | ||
|
||
// 2xx response codes. | ||
completionHandler(YES, responseJson); | ||
}]; | ||
[task resume]; | ||
return task; | ||
} | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.